mirror of https://github.com/halo-dev/halo-admin
parent
dc101e8183
commit
fd56f24b1f
|
@ -14,8 +14,6 @@ import { apiClient } from "@halo-dev/admin-shared";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
const name = useRouteQuery("name");
|
|
||||||
|
|
||||||
const initialFormState: PostRequest = {
|
const initialFormState: PostRequest = {
|
||||||
post: {
|
post: {
|
||||||
spec: {
|
spec: {
|
||||||
|
@ -60,10 +58,13 @@ const isUpdateMode = computed(() => {
|
||||||
return !!formState.value.post.metadata.creationTimestamp;
|
return !!formState.value.post.metadata.creationTimestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSavePost = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
|
|
||||||
|
// Set rendered content
|
||||||
formState.value.content.content = formState.value.content.raw;
|
formState.value.content.content = formState.value.content.raw;
|
||||||
|
|
||||||
if (isUpdateMode.value) {
|
if (isUpdateMode.value) {
|
||||||
const { data } = await apiClient.post.updateDraftPost(
|
const { data } = await apiClient.post.updateDraftPost(
|
||||||
formState.value.post.metadata.name,
|
formState.value.post.metadata.name,
|
||||||
|
@ -75,20 +76,38 @@ const handleSavePost = async () => {
|
||||||
formState.value.post = data;
|
formState.value.post = data;
|
||||||
name.value = data.metadata.name;
|
name.value = data.metadata.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await handleFetchContent();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(`保存异常: ${e}`);
|
|
||||||
console.error("Failed to save post", e);
|
console.error("Failed to save post", e);
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSettingSaved = (post: PostRequest) => {
|
const handleFetchContent = async () => {
|
||||||
formState.value = post;
|
if (!formState.value.post.spec.headSnapshot) {
|
||||||
settingModal.value = false;
|
return;
|
||||||
handleSavePost();
|
}
|
||||||
|
const { data } = await apiClient.content.obtainSnapshotContent(
|
||||||
|
formState.value.post.spec.headSnapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
formState.value.content = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSettingSaved = (post: PostRequest) => {
|
||||||
|
// Set route query parameter
|
||||||
|
if (!isUpdateMode.value) {
|
||||||
|
name.value = post.post.metadata.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
formState.value = post;
|
||||||
|
settingModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get post data when the route contains the name parameter
|
||||||
|
const name = useRouteQuery("name");
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (name.value) {
|
if (name.value) {
|
||||||
// fetch post
|
// fetch post
|
||||||
|
@ -98,12 +117,8 @@ onMounted(async () => {
|
||||||
);
|
);
|
||||||
formState.value.post = post;
|
formState.value.post = post;
|
||||||
|
|
||||||
if (formState.value.post.spec.headSnapshot) {
|
// fetch post content
|
||||||
const { data: content } = await apiClient.content.obtainSnapshotContent(
|
await handleFetchContent();
|
||||||
formState.value.post.spec.headSnapshot
|
|
||||||
);
|
|
||||||
formState.value.content = content;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -111,7 +126,6 @@ onMounted(async () => {
|
||||||
<template>
|
<template>
|
||||||
<PostSettingModal
|
<PostSettingModal
|
||||||
v-model:visible="settingModal"
|
v-model:visible="settingModal"
|
||||||
:only-emit="true"
|
|
||||||
:post="formState"
|
:post="formState"
|
||||||
@saved="onSettingSaved"
|
@saved="onSettingSaved"
|
||||||
/>
|
/>
|
||||||
|
@ -121,12 +135,7 @@ onMounted(async () => {
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<VButton
|
<VButton :loading="saving" size="sm" type="default" @click="handleSave">
|
||||||
:loading="saving"
|
|
||||||
size="sm"
|
|
||||||
type="default"
|
|
||||||
@click="handleSavePost"
|
|
||||||
>
|
|
||||||
保存
|
保存
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="secondary" @click="settingModal = true">
|
<VButton type="secondary" @click="settingModal = true">
|
||||||
|
|
|
@ -101,13 +101,17 @@ const handlePaginationChange = ({
|
||||||
handleFetchPosts();
|
handleFetchPosts();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenSettingModal = (post: Post) => {
|
const handleOpenSettingModal = async (post: Post) => {
|
||||||
selectedPost.value = post;
|
const { data } = await apiClient.extension.post.getcontentHaloRunV1alpha1Post(
|
||||||
|
post.metadata.name
|
||||||
|
);
|
||||||
|
selectedPost.value = data;
|
||||||
settingModal.value = true;
|
settingModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSettingModalClose = () => {
|
const onSettingModalClose = () => {
|
||||||
selectedPost.value = null;
|
selectedPost.value = null;
|
||||||
|
selectedPostWithContent.value = null;
|
||||||
handleFetchPosts();
|
handleFetchPosts();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -117,7 +121,11 @@ const handleSelectPrevious = async () => {
|
||||||
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
||||||
);
|
);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
selectedPost.value = items[index - 1].post;
|
const { data } =
|
||||||
|
await apiClient.extension.post.getcontentHaloRunV1alpha1Post(
|
||||||
|
items[index - 1].post.metadata.name
|
||||||
|
);
|
||||||
|
selectedPost.value = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (index === 0 && hasPrevious) {
|
if (index === 0 && hasPrevious) {
|
||||||
|
@ -133,7 +141,11 @@ const handleSelectNext = async () => {
|
||||||
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
||||||
);
|
);
|
||||||
if (index < items.length - 1) {
|
if (index < items.length - 1) {
|
||||||
selectedPost.value = items[index + 1].post;
|
const { data } =
|
||||||
|
await apiClient.extension.post.getcontentHaloRunV1alpha1Post(
|
||||||
|
items[index + 1].post.metadata.name
|
||||||
|
);
|
||||||
|
selectedPost.value = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (index === items.length - 1 && hasNext) {
|
if (index === items.length - 1 && hasNext) {
|
||||||
|
@ -625,7 +637,7 @@ function handlePhaseFilterItemChange(filterItem: FilterItem) {
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<VButton @click="handleFetchPosts">刷新</VButton>
|
<VButton @click="handleFetchPosts">刷新</VButton>
|
||||||
<VButton type="primary" :route="{ name: 'PostEditor' }">
|
<VButton :route="{ name: 'PostEditor' }" type="primary">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -88,6 +88,12 @@ const onVisibleChange = (visible: boolean) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetForm = () => {
|
||||||
|
formState.value = cloneDeep(initialFormState);
|
||||||
|
formState.value.metadata.name = uuid();
|
||||||
|
reset("category-form");
|
||||||
|
};
|
||||||
|
|
||||||
const { Command_Enter } = useMagicKeys();
|
const { Command_Enter } = useMagicKeys();
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
@ -99,13 +105,20 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible && props.category) {
|
if (!visible) {
|
||||||
formState.value = cloneDeep(props.category);
|
handleResetForm();
|
||||||
return;
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.category,
|
||||||
|
(category) => {
|
||||||
|
if (category) {
|
||||||
|
formState.value = cloneDeep(category);
|
||||||
|
} else {
|
||||||
|
handleResetForm();
|
||||||
}
|
}
|
||||||
formState.value = cloneDeep(initialFormState);
|
|
||||||
reset("category-form");
|
|
||||||
formState.value.metadata.name = uuid();
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -88,7 +88,9 @@ function onDelete(category: CategoryTree) {
|
||||||
</div>
|
</div>
|
||||||
<template #popper> 删除中</template>
|
<template #popper> 删除中</template>
|
||||||
</FloatingTooltip>
|
</FloatingTooltip>
|
||||||
|
<!--TODO: Get post count-->
|
||||||
<div
|
<div
|
||||||
|
v-if="false"
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||||
>
|
>
|
||||||
20 篇文章
|
20 篇文章
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
|
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
|
||||||
import { computed, ref, watch, watchEffect } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
import type { PostRequest } from "@halo-dev/api-client";
|
import type { PostRequest } from "@halo-dev/api-client";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
||||||
|
@ -48,12 +48,10 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
post?: PostRequest | null;
|
post?: PostRequest | null;
|
||||||
onlyEmit?: boolean;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
visible: false,
|
visible: false,
|
||||||
post: null,
|
post: null,
|
||||||
onlyEmit: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -100,11 +98,7 @@ const handleVisibleChange = (visible: boolean) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveOnly = async () => {
|
const handleSave = async () => {
|
||||||
if (props.onlyEmit) {
|
|
||||||
emit("saved", formState.value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
if (isUpdateMode.value) {
|
if (isUpdateMode.value) {
|
||||||
|
@ -129,13 +123,24 @@ const handleSaveOnly = async () => {
|
||||||
const handlePublish = async () => {
|
const handlePublish = async () => {
|
||||||
try {
|
try {
|
||||||
publishing.value = true;
|
publishing.value = true;
|
||||||
|
|
||||||
|
// Save post
|
||||||
|
await handleSave();
|
||||||
|
|
||||||
|
// Get latest version post
|
||||||
|
const { data: latestData } =
|
||||||
|
await apiClient.extension.post.getcontentHaloRunV1alpha1Post(
|
||||||
|
formState.value.post.metadata.name
|
||||||
|
);
|
||||||
|
formState.value.post = latestData;
|
||||||
|
|
||||||
|
// Publish post
|
||||||
const { data } = await apiClient.post.publishPost(
|
const { data } = await apiClient.post.publishPost(
|
||||||
formState.value.post.metadata.name
|
formState.value.post.metadata.name
|
||||||
);
|
);
|
||||||
formState.value.post = data;
|
formState.value.post = data;
|
||||||
emit("saved", formState.value);
|
emit("saved", formState.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(`发布异常: ${e}`);
|
|
||||||
console.error("Failed to publish post", e);
|
console.error("Failed to publish post", e);
|
||||||
} finally {
|
} finally {
|
||||||
publishing.value = false;
|
publishing.value = false;
|
||||||
|
@ -145,15 +150,22 @@ const handlePublish = async () => {
|
||||||
const handlePublishCanceling = async () => {
|
const handlePublishCanceling = async () => {
|
||||||
try {
|
try {
|
||||||
publishCanceling.value = true;
|
publishCanceling.value = true;
|
||||||
|
|
||||||
|
// Update published spec = false
|
||||||
const postToUpdate = cloneDeep(formState.value);
|
const postToUpdate = cloneDeep(formState.value);
|
||||||
postToUpdate.post.spec.published = false;
|
postToUpdate.post.spec.published = false;
|
||||||
|
await apiClient.post.updateDraftPost(
|
||||||
const { data } = await apiClient.post.updateDraftPost(
|
|
||||||
postToUpdate.post.metadata.name,
|
postToUpdate.post.metadata.name,
|
||||||
postToUpdate
|
postToUpdate
|
||||||
);
|
);
|
||||||
|
|
||||||
formState.value.post = data;
|
// Get latest version post
|
||||||
|
const { data: latestData } =
|
||||||
|
await apiClient.extension.post.getcontentHaloRunV1alpha1Post(
|
||||||
|
formState.value.post.metadata.name
|
||||||
|
);
|
||||||
|
|
||||||
|
formState.value.post = latestData;
|
||||||
emit("saved", formState.value);
|
emit("saved", formState.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to cancel publish", e);
|
console.log("Failed to cancel publish", e);
|
||||||
|
@ -162,18 +174,6 @@ const handlePublishCanceling = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(visible) => {
|
|
||||||
if (visible && props.post) {
|
|
||||||
formState.value = cloneDeep(props.post);
|
|
||||||
}
|
|
||||||
if (!visible) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.post) {
|
if (props.post) {
|
||||||
formState.value = cloneDeep(props.post);
|
formState.value = cloneDeep(props.post);
|
||||||
|
@ -288,27 +288,7 @@ watchEffect(() => {
|
||||||
></FormKit>
|
></FormKit>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
</VTabItem>
|
</VTabItem>
|
||||||
<VTabItem id="seo" label="SEO">
|
<!--TODO: add SEO/Metas/Inject Code form-->
|
||||||
<FormKit id="seo" :actions="false" :preserve="true" type="form">
|
|
||||||
<FormKit
|
|
||||||
label="自定义关键词"
|
|
||||||
name="metaKeywords"
|
|
||||||
type="textarea"
|
|
||||||
></FormKit>
|
|
||||||
<FormKit
|
|
||||||
label="自定义描述"
|
|
||||||
name="metaDescription"
|
|
||||||
type="textarea"
|
|
||||||
></FormKit>
|
|
||||||
</FormKit>
|
|
||||||
</VTabItem>
|
|
||||||
<VTabItem id="metas" label="元数据"></VTabItem>
|
|
||||||
<VTabItem id="inject-code" label="代码注入">
|
|
||||||
<FormKit id="inject-code" :actions="false" :preserve="true" type="form">
|
|
||||||
<FormKit label="CSS" type="textarea"></FormKit>
|
|
||||||
<FormKit label="JavaScript" type="textarea"></FormKit>
|
|
||||||
</FormKit>
|
|
||||||
</VTabItem>
|
|
||||||
</VTabs>
|
</VTabs>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
@ -323,7 +303,6 @@ watchEffect(() => {
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-else
|
v-else
|
||||||
:disabled="!isUpdateMode"
|
|
||||||
:loading="publishing"
|
:loading="publishing"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="handlePublish"
|
@click="handlePublish"
|
||||||
|
@ -334,7 +313,7 @@ watchEffect(() => {
|
||||||
:loading="saving"
|
:loading="saving"
|
||||||
size="sm"
|
size="sm"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="handleSaveOnly"
|
@click="handleSave"
|
||||||
>
|
>
|
||||||
仅保存
|
仅保存
|
||||||
</VButton>
|
</VButton>
|
||||||
|
|
|
@ -207,7 +207,9 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
<template #popper> 删除中</template>
|
<template #popper> 删除中</template>
|
||||||
</FloatingTooltip>
|
</FloatingTooltip>
|
||||||
|
<!--TODO: Get post count-->
|
||||||
<div
|
<div
|
||||||
|
v-if="false"
|
||||||
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
class="cursor-pointer text-sm text-gray-500 hover:text-gray-900"
|
||||||
>
|
>
|
||||||
20 篇文章
|
20 篇文章
|
||||||
|
|
Loading…
Reference in New Issue