mirror of https://github.com/halo-dev/halo-admin
feat: add setup post/singlePage/category custom templates support (#671)
#### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 支持为文章/自定义页面/分类设置自定义模板。适配 https://github.com/halo-dev/halo/pull/2638 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2569 #### Screenshots: <img width="625" alt="image" src="https://user-images.githubusercontent.com/21301288/198823380-991a702d-aae7-4587-b0f8-81fcb018a1f6.png"> #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/2638 PR 的分支。 2. 根据 https://github.com/halo-dev/halo/pull/2638 PR 中的描述修改主题配置 `theme.yaml`,添加所需测试的模板配置。 3. 检查 Console 对应的设置项(分类编辑、文章设置、自定义页面)中的自定义模板选择框是否包含配置的模板。 4. 选择配置的模板后保存。检查主题端对应页面是否一致。 #### Does this PR introduce a user-facing change? ```release-note 支持为文章/自定义页面/分类设置自定义模板。 ```pull/678/head
parent
3d638fde37
commit
ff26058fc0
|
@ -33,7 +33,7 @@
|
||||||
"@formkit/inputs": "^1.0.0-beta.11",
|
"@formkit/inputs": "^1.0.0-beta.11",
|
||||||
"@formkit/themes": "^1.0.0-beta.11",
|
"@formkit/themes": "^1.0.0-beta.11",
|
||||||
"@formkit/vue": "^1.0.0-beta.11",
|
"@formkit/vue": "^1.0.0-beta.11",
|
||||||
"@halo-dev/api-client": "^0.0.39",
|
"@halo-dev/api-client": "^0.0.40",
|
||||||
"@halo-dev/components": "workspace:*",
|
"@halo-dev/components": "workspace:*",
|
||||||
"@halo-dev/console-shared": "workspace:*",
|
"@halo-dev/console-shared": "workspace:*",
|
||||||
"@halo-dev/richtext-editor": "^0.0.0-alpha.8",
|
"@halo-dev/richtext-editor": "^0.0.0-alpha.8",
|
||||||
|
|
|
@ -13,7 +13,7 @@ importers:
|
||||||
'@formkit/inputs': ^1.0.0-beta.11
|
'@formkit/inputs': ^1.0.0-beta.11
|
||||||
'@formkit/themes': ^1.0.0-beta.11
|
'@formkit/themes': ^1.0.0-beta.11
|
||||||
'@formkit/vue': ^1.0.0-beta.11
|
'@formkit/vue': ^1.0.0-beta.11
|
||||||
'@halo-dev/api-client': ^0.0.39
|
'@halo-dev/api-client': ^0.0.40
|
||||||
'@halo-dev/components': workspace:*
|
'@halo-dev/components': workspace:*
|
||||||
'@halo-dev/console-shared': workspace:*
|
'@halo-dev/console-shared': workspace:*
|
||||||
'@halo-dev/richtext-editor': ^0.0.0-alpha.8
|
'@halo-dev/richtext-editor': ^0.0.0-alpha.8
|
||||||
|
@ -108,7 +108,7 @@ importers:
|
||||||
'@formkit/inputs': 1.0.0-beta.11
|
'@formkit/inputs': 1.0.0-beta.11
|
||||||
'@formkit/themes': 1.0.0-beta.11_tailwindcss@3.2.1
|
'@formkit/themes': 1.0.0-beta.11_tailwindcss@3.2.1
|
||||||
'@formkit/vue': 1.0.0-beta.11_vjnbgdptsk6bkj7ab5a6mk2cwm
|
'@formkit/vue': 1.0.0-beta.11_vjnbgdptsk6bkj7ab5a6mk2cwm
|
||||||
'@halo-dev/api-client': 0.0.39
|
'@halo-dev/api-client': 0.0.40
|
||||||
'@halo-dev/components': link:packages/components
|
'@halo-dev/components': link:packages/components
|
||||||
'@halo-dev/console-shared': link:packages/shared
|
'@halo-dev/console-shared': link:packages/shared
|
||||||
'@halo-dev/richtext-editor': 0.0.0-alpha.8_vue@3.2.41
|
'@halo-dev/richtext-editor': 0.0.0-alpha.8_vue@3.2.41
|
||||||
|
@ -1953,8 +1953,8 @@ packages:
|
||||||
- windicss
|
- windicss
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/api-client/0.0.39:
|
/@halo-dev/api-client/0.0.40:
|
||||||
resolution: {integrity: sha512-GuTTJDOj0PPMXo3KTiNYGACRUXqJKnjnApK303eNPWqVodgR3mJVLFTXwa+euAJOkcSG3KkB5OtUFAkZeHRbPA==}
|
resolution: {integrity: sha512-SsB+PZkoCwp0cGKGQY+x2oDWiZDwHYH9nytN28b+kY+HnWmNIWgMT64ZnHoHxOnk44m9S9byxwkkvlOamby0AQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@halo-dev/richtext-editor/0.0.0-alpha.8_vue@3.2.41:
|
/@halo-dev/richtext-editor/0.0.0-alpha.8_vue@3.2.41:
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
IconPages,
|
IconPages,
|
||||||
IconUserSettings,
|
IconUserSettings,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { computed, markRaw, onMounted, ref, watch, type Component } from "vue";
|
import { computed, markRaw, ref, watch, type Component } from "vue";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
@ -356,6 +356,8 @@ watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
handleBuildSearchIndex();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
globalSearchInput.value?.focus();
|
globalSearchInput.value?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -369,10 +371,6 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
handleBuildSearchIndex();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onVisibleChange = (visible: boolean) => {
|
const onVisibleChange = (visible: boolean) => {
|
||||||
emit("update:visible", visible);
|
emit("update:visible", visible);
|
||||||
};
|
};
|
||||||
|
|
21
src/main.ts
21
src/main.ts
|
@ -1,5 +1,5 @@
|
||||||
import type { DirectiveBinding } from "vue";
|
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
import type { DirectiveBinding } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
@ -17,6 +17,7 @@ import type { User } from "@halo-dev/api-client";
|
||||||
import { hasPermission } from "@/utils/permission";
|
import { hasPermission } from "@/utils/permission";
|
||||||
import { useRoleStore } from "@/stores/role";
|
import { useRoleStore } from "@/stores/role";
|
||||||
import type { RouteRecordRaw } from "vue-router";
|
import type { RouteRecordRaw } from "vue-router";
|
||||||
|
import { useThemeStore } from "./stores/theme";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
@ -175,9 +176,12 @@ async function loadPluginModules() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentUser: User | undefined = undefined;
|
||||||
|
|
||||||
async function loadCurrentUser() {
|
async function loadCurrentUser() {
|
||||||
const { data: user } = await apiClient.user.getCurrentUserDetail();
|
const { data: user } = await apiClient.user.getCurrentUserDetail();
|
||||||
app.provide<User>("currentUser", user);
|
app.provide<User>("currentUser", user);
|
||||||
|
currentUser = user;
|
||||||
|
|
||||||
const { data: currentPermissions } = await apiClient.user.getPermissions({
|
const { data: currentPermissions } = await apiClient.user.getPermissions({
|
||||||
name: "-",
|
name: "-",
|
||||||
|
@ -204,6 +208,11 @@ async function loadCurrentUser() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadActivatedTheme() {
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
await themeStore.fetchActivatedTheme();
|
||||||
|
}
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
await initApp();
|
await initApp();
|
||||||
})();
|
})();
|
||||||
|
@ -218,13 +227,19 @@ async function initApp() {
|
||||||
try {
|
try {
|
||||||
loadCoreModules();
|
loadCoreModules();
|
||||||
|
|
||||||
|
await loadCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadActivatedTheme();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await loadPluginModules();
|
await loadPluginModules();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load plugins", e);
|
console.error("Failed to load plugins", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadCurrentUser();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { SinglePageRequest } from "@halo-dev/api-client";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/use-theme";
|
||||||
|
|
||||||
const initialFormState: SinglePageRequest = {
|
const initialFormState: SinglePageRequest = {
|
||||||
page: {
|
page: {
|
||||||
|
@ -160,6 +161,9 @@ watchEffect(() => {
|
||||||
formState.value = cloneDeep(props.singlePage);
|
formState.value = cloneDeep(props.singlePage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// custom templates
|
||||||
|
const { templates } = useThemeCustomTemplates("page");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -265,8 +269,9 @@ watchEffect(() => {
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.page.spec.template"
|
v-model="formState.page.spec.template"
|
||||||
|
:options="templates"
|
||||||
label="自定义模板"
|
label="自定义模板"
|
||||||
type="text"
|
type="select"
|
||||||
name="template"
|
name="template"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
|
|
@ -15,6 +15,7 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
import { reset } from "@formkit/core";
|
import { reset } from "@formkit/core";
|
||||||
import { setFocus } from "@/formkit/utils/focus";
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/use-theme";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -116,6 +117,9 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// custom templates
|
||||||
|
const { templates } = useThemeCustomTemplates("category");
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<VModal
|
||||||
|
@ -147,6 +151,13 @@ watch(
|
||||||
type="text"
|
type="text"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-model="formState.spec.template"
|
||||||
|
:options="templates"
|
||||||
|
label="自定义模板"
|
||||||
|
type="select"
|
||||||
|
name="template"
|
||||||
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.spec.cover"
|
v-model="formState.spec.cover"
|
||||||
help="需要主题适配以支持"
|
help="需要主题适配以支持"
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { beforeEach, describe, expect, it } from "vitest";
|
||||||
import { mount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import CategoryEditingModal from "../CategoryEditingModal.vue";
|
import CategoryEditingModal from "../CategoryEditingModal.vue";
|
||||||
|
import { createPinia, setActivePinia } from "pinia";
|
||||||
|
|
||||||
describe("CategoryEditingModal", function () {
|
describe("CategoryEditingModal", function () {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
});
|
||||||
|
|
||||||
it("should render", function () {
|
it("should render", function () {
|
||||||
expect(mount(CategoryEditingModal)).toBeDefined();
|
expect(mount(CategoryEditingModal)).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { PostRequest } from "@halo-dev/api-client";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/use-theme";
|
||||||
|
|
||||||
const initialFormState: PostRequest = {
|
const initialFormState: PostRequest = {
|
||||||
post: {
|
post: {
|
||||||
|
@ -163,6 +164,9 @@ watchEffect(() => {
|
||||||
formState.value = cloneDeep(props.post);
|
formState.value = cloneDeep(props.post);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// custom templates
|
||||||
|
const { templates } = useThemeCustomTemplates("post");
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<VModal
|
||||||
|
@ -277,9 +281,10 @@ watchEffect(() => {
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.post.spec.template"
|
v-model="formState.post.spec.template"
|
||||||
|
:options="templates"
|
||||||
label="自定义模板"
|
label="自定义模板"
|
||||||
name="template"
|
name="template"
|
||||||
type="text"
|
type="select"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.post.spec.cover"
|
v-model="formState.post.spec.cover"
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { beforeEach, describe, expect, it } from "vitest";
|
||||||
import { mount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import PostSettingModal from "../PostSettingModal.vue";
|
import PostSettingModal from "../PostSettingModal.vue";
|
||||||
import { VDialogProvider } from "@halo-dev/components";
|
import { createPinia, setActivePinia } from "pinia";
|
||||||
|
|
||||||
describe("PostSettingModal", () => {
|
describe("PostSettingModal", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
});
|
||||||
|
|
||||||
it("should render", () => {
|
it("should render", () => {
|
||||||
const wrapper = mount({
|
const wrapper = mount({
|
||||||
components: {
|
components: {
|
||||||
VDialogProvider,
|
|
||||||
PostSettingModal,
|
PostSettingModal,
|
||||||
},
|
},
|
||||||
template: `
|
template: `<PostSettingModal></PostSettingModal>`,
|
||||||
<VDialogProvider>
|
|
||||||
<PostSettingModal></PostSettingModal>
|
|
||||||
</VDialogProvider>`,
|
|
||||||
});
|
});
|
||||||
expect(wrapper).toBeDefined();
|
expect(wrapper).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,8 @@ import type { Theme } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { onBeforeRouteLeave } from "vue-router";
|
import { onBeforeRouteLeave } from "vue-router";
|
||||||
|
import { useThemeStore } from "@/stores/theme";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -31,12 +33,10 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
selectedTheme: Theme | null;
|
selectedTheme: Theme | null;
|
||||||
activatedTheme: Theme | null;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
visible: false,
|
visible: false,
|
||||||
selectedTheme: null,
|
selectedTheme: null,
|
||||||
activatedTheme: null,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -58,6 +58,8 @@ const modalTitle = computed(() => {
|
||||||
return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题";
|
return activeTab.value === "installed" ? "已安装的主题" : "未安装的主题";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { activatedTheme } = storeToRefs(useThemeStore());
|
||||||
|
|
||||||
const handleFetchThemes = async () => {
|
const handleFetchThemes = async () => {
|
||||||
try {
|
try {
|
||||||
clearInterval(refreshInterval.value);
|
clearInterval(refreshInterval.value);
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import type { ComputedRef, Ref } from "vue";
|
import type { ComputedRef, Ref } from "vue";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import type { Theme } from "@halo-dev/api-client";
|
import type { Theme } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { Dialog } from "@halo-dev/components";
|
import { Dialog } from "@halo-dev/components";
|
||||||
|
import { useThemeStore } from "@/stores/theme";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
interface useThemeLifeCycleReturn {
|
interface useThemeLifeCycleReturn {
|
||||||
loading: Ref<boolean>;
|
loading: Ref<boolean>;
|
||||||
activatedTheme: Ref<Theme | undefined>;
|
|
||||||
isActivated: ComputedRef<boolean>;
|
isActivated: ComputedRef<boolean>;
|
||||||
handleActiveTheme: () => void;
|
handleActiveTheme: () => void;
|
||||||
}
|
}
|
||||||
|
@ -14,43 +15,16 @@ interface useThemeLifeCycleReturn {
|
||||||
export function useThemeLifeCycle(
|
export function useThemeLifeCycle(
|
||||||
theme: Ref<Theme | undefined>
|
theme: Ref<Theme | undefined>
|
||||||
): useThemeLifeCycleReturn {
|
): useThemeLifeCycleReturn {
|
||||||
const activatedTheme = ref<Theme | undefined>();
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
|
const { activatedTheme } = storeToRefs(themeStore);
|
||||||
|
|
||||||
const isActivated = computed(() => {
|
const isActivated = computed(() => {
|
||||||
return activatedTheme.value?.metadata.name === theme.value?.metadata.name;
|
return activatedTheme?.value?.metadata.name === theme.value?.metadata.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFetchActivatedTheme = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
const { data } = await apiClient.extension.configMap.getv1alpha1ConfigMap(
|
|
||||||
{
|
|
||||||
name: "system",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!data.data?.theme) {
|
|
||||||
// Todo: show error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const themeConfig = JSON.parse(data.data.theme);
|
|
||||||
|
|
||||||
const { data: themeData } =
|
|
||||||
await apiClient.extension.theme.getthemeHaloRunV1alpha1Theme({
|
|
||||||
name: themeConfig.active,
|
|
||||||
});
|
|
||||||
|
|
||||||
theme.value = themeData;
|
|
||||||
activatedTheme.value = themeData;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to fetch active theme", e);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleActiveTheme = async () => {
|
const handleActiveTheme = async () => {
|
||||||
Dialog.info({
|
Dialog.info({
|
||||||
title: "是否确认启用当前主题",
|
title: "是否确认启用当前主题",
|
||||||
|
@ -77,18 +51,48 @@ export function useThemeLifeCycle(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to active theme", e);
|
console.error("Failed to active theme", e);
|
||||||
} finally {
|
} finally {
|
||||||
await handleFetchActivatedTheme();
|
themeStore.fetchActivatedTheme();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(handleFetchActivatedTheme);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
activatedTheme,
|
|
||||||
isActivated,
|
isActivated,
|
||||||
handleActiveTheme,
|
handleActiveTheme,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useThemeCustomTemplates(type: "post" | "page" | "category") {
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const templates = computed(() => {
|
||||||
|
const defaultTemplate = [
|
||||||
|
{
|
||||||
|
label: "默认模板",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!themeStore.activatedTheme) {
|
||||||
|
return defaultTemplate;
|
||||||
|
}
|
||||||
|
const { customTemplates } = themeStore.activatedTheme.spec;
|
||||||
|
if (!customTemplates?.[type]) {
|
||||||
|
return defaultTemplate;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...defaultTemplate,
|
||||||
|
...(customTemplates[type]?.map((template) => {
|
||||||
|
return {
|
||||||
|
value: template.file,
|
||||||
|
label: template.name || template.file,
|
||||||
|
};
|
||||||
|
}) || []),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
templates,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// core libs
|
// core libs
|
||||||
import { nextTick, type ComputedRef, type Ref } from "vue";
|
import { nextTick, onMounted, type ComputedRef, type Ref } from "vue";
|
||||||
import { computed, provide, ref, watch } from "vue";
|
import { computed, provide, ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ import {
|
||||||
import ThemeListModal from "../components/ThemeListModal.vue";
|
import ThemeListModal from "../components/ThemeListModal.vue";
|
||||||
import type { SettingForm, Theme } from "@halo-dev/api-client";
|
import type { SettingForm, Theme } from "@halo-dev/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
import { useThemeStore } from "@/stores/theme";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
|
@ -55,7 +57,7 @@ const selectedTheme = ref<Theme | undefined>();
|
||||||
const themesModal = ref(false);
|
const themesModal = ref(false);
|
||||||
const activeTab = ref("");
|
const activeTab = ref("");
|
||||||
|
|
||||||
const { loading, isActivated, activatedTheme, handleActiveTheme } =
|
const { loading, isActivated, handleActiveTheme } =
|
||||||
useThemeLifeCycle(selectedTheme);
|
useThemeLifeCycle(selectedTheme);
|
||||||
|
|
||||||
const settingName = computed(() => selectedTheme.value?.spec.settingName);
|
const settingName = computed(() => selectedTheme.value?.spec.settingName);
|
||||||
|
@ -143,11 +145,18 @@ const handleTriggerTabChange = () => {
|
||||||
watch([() => route.name, () => route.params], async () => {
|
watch([() => route.name, () => route.params], async () => {
|
||||||
handleTriggerTabChange();
|
handleTriggerTabChange();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
|
const { activatedTheme } = storeToRefs(themeStore);
|
||||||
|
|
||||||
|
selectedTheme.value = activatedTheme?.value;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<BasicLayout>
|
<BasicLayout>
|
||||||
<ThemeListModal
|
<ThemeListModal
|
||||||
v-model:activated-theme="activatedTheme"
|
|
||||||
v-model:selected-theme="selectedTheme"
|
v-model:selected-theme="selectedTheme"
|
||||||
v-model:visible="themesModal"
|
v-model:visible="themesModal"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { defineStore } from "pinia";
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore({
|
|
||||||
id: "counter",
|
|
||||||
state: () => ({
|
|
||||||
counter: 0,
|
|
||||||
}),
|
|
||||||
getters: {
|
|
||||||
doubleCount: (state) => state.counter * 2,
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
increment() {
|
|
||||||
this.counter++;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
import type { Theme } from "@halo-dev/api-client";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
interface ThemeStoreState {
|
||||||
|
activatedTheme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useThemeStore = defineStore("theme", {
|
||||||
|
state: (): ThemeStoreState => ({
|
||||||
|
activatedTheme: undefined,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async fetchActivatedTheme() {
|
||||||
|
try {
|
||||||
|
const { data } =
|
||||||
|
await apiClient.extension.configMap.getv1alpha1ConfigMap({
|
||||||
|
name: "system",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data.data?.theme) {
|
||||||
|
// Todo: show error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeConfig = JSON.parse(data.data.theme);
|
||||||
|
|
||||||
|
const { data: themeData } =
|
||||||
|
await apiClient.extension.theme.getthemeHaloRunV1alpha1Theme({
|
||||||
|
name: themeConfig.active,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activatedTheme = themeData;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch active theme", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue