mirror of https://github.com/halo-dev/halo
refactor: logic of publishing post (#5987)
#### What type of PR is this? /area ui /kind improvement /milestone 2.16.x #### What this PR does / why we need it: 重构文章的发布逻辑,以下是主要改动: 1. 如果文章未发布,点击文章设置的发布按钮时,会先保存文章。 2. 在文章列表的操作菜单中添加发布 / 取消发布的选项。 3. 重构文章设置中,发布 / 取消发布按钮的显示条件。 4. 优化文章设置对话框的显示条件,减少不必要的渲染开销和请求。 #### Special notes for your reviewer: 需要测试: 1. 文章正常新建和发布的逻辑。 2. 文章设置未来时间的发布逻辑。 3. 取消定时发布和已发布文章的逻辑。 #### Does this PR introduce a user-facing change? ```release-note 优化 Console 文章管理中的文章发布逻辑。 ```pull/5994/head^2
parent
6124ab9831
commit
54e088741e
|
@ -131,16 +131,14 @@ const {
|
||||||
return data.items;
|
return data.items;
|
||||||
},
|
},
|
||||||
refetchInterval(data) {
|
refetchInterval(data) {
|
||||||
const abnormalSinglePages = data?.filter((singlePage) => {
|
const hasAbnormalSinglePage = data?.some((singlePage) => {
|
||||||
const { spec, metadata, status } = singlePage.page;
|
const { spec, metadata } = singlePage.page;
|
||||||
return (
|
return (
|
||||||
spec.deleted ||
|
spec.deleted ||
|
||||||
(spec.publish &&
|
metadata.labels?.[singlePageLabels.PUBLISHED] !== spec.publish + ""
|
||||||
metadata.labels?.[singlePageLabels.PUBLISHED] !== "true") ||
|
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return abnormalSinglePages?.length ? 1000 : false;
|
return hasAbnormalSinglePage ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -67,8 +67,8 @@ const formState = ref<SinglePage>({
|
||||||
name: randomUUID(),
|
name: randomUUID(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const modal = ref();
|
const modal = ref<InstanceType<typeof VModal>>();
|
||||||
const saving = ref(false);
|
const isSubmitting = ref(false);
|
||||||
const publishing = ref(false);
|
const publishing = ref(false);
|
||||||
const publishCanceling = ref(false);
|
const publishCanceling = ref(false);
|
||||||
const submitType = ref<"publish" | "save">();
|
const submitType = ref<"publish" | "save">();
|
||||||
|
@ -125,12 +125,12 @@ const handleSave = async () => {
|
||||||
|
|
||||||
if (props.onlyEmit) {
|
if (props.onlyEmit) {
|
||||||
emit("saved", formState.value);
|
emit("saved", formState.value);
|
||||||
modal.value.close();
|
modal.value?.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saving.value = true;
|
isSubmitting.value = true;
|
||||||
|
|
||||||
const { data } = isUpdateMode
|
const { data } = isUpdateMode
|
||||||
? await singlePageUpdateMutate(formState.value)
|
? await singlePageUpdateMutate(formState.value)
|
||||||
|
@ -143,13 +143,13 @@ const handleSave = async () => {
|
||||||
formState.value = data;
|
formState.value = data;
|
||||||
emit("saved", data);
|
emit("saved", data);
|
||||||
|
|
||||||
modal.value.close();
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.save_success"));
|
Toast.success(t("core.common.toast.save_success"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save single page", error);
|
console.error("Failed to save single page", error);
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
isSubmitting.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ const handlePublish = async () => {
|
||||||
|
|
||||||
if (props.onlyEmit) {
|
if (props.onlyEmit) {
|
||||||
emit("published", formState.value);
|
emit("published", formState.value);
|
||||||
modal.value.close();
|
modal.value?.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ const handlePublish = async () => {
|
||||||
|
|
||||||
emit("published", data);
|
emit("published", data);
|
||||||
|
|
||||||
modal.value.close();
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.publish_success"));
|
Toast.success(t("core.common.toast.publish_success"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -227,7 +227,7 @@ const handleUnpublish = async () => {
|
||||||
|
|
||||||
formState.value = data;
|
formState.value = data;
|
||||||
|
|
||||||
modal.value.close();
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.cancel_publish_success"));
|
Toast.success(t("core.common.toast.cancel_publish_success"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -457,10 +457,11 @@ const { handleGenerateSlug } = useSlugify(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<template v-if="publishSupport">
|
|
||||||
<VButton
|
<VButton
|
||||||
v-if="
|
v-if="
|
||||||
|
publishSupport &&
|
||||||
formState.metadata.labels?.[singlePageLabels.PUBLISHED] !== 'true'
|
formState.metadata.labels?.[singlePageLabels.PUBLISHED] !== 'true'
|
||||||
"
|
"
|
||||||
:loading="publishing"
|
:loading="publishing"
|
||||||
|
@ -470,21 +471,28 @@ const { handleGenerateSlug } = useSlugify(
|
||||||
{{ $t("core.common.buttons.publish") }}
|
{{ $t("core.common.buttons.publish") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-else
|
:loading="isSubmitting"
|
||||||
|
type="secondary"
|
||||||
|
@click="handleSaveClick"
|
||||||
|
>
|
||||||
|
{{ $t("core.common.buttons.save") }}
|
||||||
|
</VButton>
|
||||||
|
<VButton type="default" @click="modal?.close()">
|
||||||
|
{{ $t("core.common.buttons.close") }}
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
|
|
||||||
|
<VButton
|
||||||
|
v-if="
|
||||||
|
formState.metadata.labels?.[singlePageLabels.PUBLISHED] === 'true'
|
||||||
|
"
|
||||||
:loading="publishCanceling"
|
:loading="publishCanceling"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleUnpublish()"
|
@click="handleUnpublish()"
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.cancel_publish") }}
|
{{ $t("core.common.buttons.cancel_publish") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
</template>
|
</div>
|
||||||
<VButton :loading="saving" type="secondary" @click="handleSaveClick">
|
|
||||||
{{ $t("core.common.buttons.save") }}
|
|
||||||
</VButton>
|
|
||||||
<VButton type="default" @click="modal.close()">
|
|
||||||
{{ $t("core.common.buttons.close") }}
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -342,7 +342,6 @@ const onSettingSaved = (post: Post) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
formState.value.post = post;
|
formState.value.post = post;
|
||||||
settingModal.value = false;
|
|
||||||
|
|
||||||
if (!isUpdateMode.value) {
|
if (!isUpdateMode.value) {
|
||||||
handleSave();
|
handleSave();
|
||||||
|
@ -351,7 +350,6 @@ const onSettingSaved = (post: Post) => {
|
||||||
|
|
||||||
const onSettingPublished = (post: Post) => {
|
const onSettingPublished = (post: Post) => {
|
||||||
formState.value.post = post;
|
formState.value.post = post;
|
||||||
settingModal.value = false;
|
|
||||||
handlePublish();
|
handlePublish();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -454,10 +452,11 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PostSettingModal
|
<PostSettingModal
|
||||||
v-model:visible="settingModal"
|
v-if="settingModal"
|
||||||
:post="formState.post"
|
:post="formState.post"
|
||||||
:publish-support="!isUpdateMode"
|
:publish-support="!isUpdateMode"
|
||||||
:only-emit="!isUpdateMode"
|
:only-emit="!isUpdateMode"
|
||||||
|
@close="settingModal = false"
|
||||||
@saved="onSettingSaved"
|
@saved="onSettingSaved"
|
||||||
@published="onSettingPublished"
|
@published="onSettingPublished"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
|
Dialog,
|
||||||
IconAddCircle,
|
IconAddCircle,
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
IconBookRead,
|
IconBookRead,
|
||||||
IconRefreshLine,
|
IconRefreshLine,
|
||||||
Dialog,
|
Toast,
|
||||||
VButton,
|
VButton,
|
||||||
VCard,
|
VCard,
|
||||||
VEmpty,
|
VEmpty,
|
||||||
|
VLoading,
|
||||||
VPageHeader,
|
VPageHeader,
|
||||||
VPagination,
|
VPagination,
|
||||||
VSpace,
|
VSpace,
|
||||||
VLoading,
|
|
||||||
Toast,
|
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import PostSettingModal from "./components/PostSettingModal.vue";
|
import PostSettingModal from "./components/PostSettingModal.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type { Post, ListedPost } from "@halo-dev/api-client";
|
import { computed, provide, ref, watch } from "vue";
|
||||||
|
import type { ListedPost, Post } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { postLabels } from "@/constants/labels";
|
import { postLabels } from "@/constants/labels";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
|
@ -27,8 +28,6 @@ import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
||||||
import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue";
|
import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue";
|
||||||
import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue";
|
import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue";
|
||||||
import PostListItem from "./components/PostListItem.vue";
|
import PostListItem from "./components/PostListItem.vue";
|
||||||
import { provide } from "vue";
|
|
||||||
import type { Ref } from "vue";
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -152,18 +151,34 @@ const {
|
||||||
return data.items;
|
return data.items;
|
||||||
},
|
},
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const abnormalPosts = data?.some((post) => {
|
const hasDeletingPost = data?.some((post) => post.post.spec.deleted);
|
||||||
const { spec, metadata, status } = post.post;
|
|
||||||
|
if (hasDeletingPost) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPublishingPost = data?.some((post) => {
|
||||||
|
const { spec, metadata } = post.post;
|
||||||
return (
|
return (
|
||||||
spec.deleted ||
|
metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
|
||||||
(spec.publish &&
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
|
||||||
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
|
||||||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
|
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return abnormalPosts ? 1000 : false;
|
if (hasPublishingPost) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCancelingPublishPost = data?.some((post) => {
|
||||||
|
const { spec, metadata } = post.post;
|
||||||
|
return (
|
||||||
|
!spec.publish &&
|
||||||
|
(metadata.labels?.[postLabels.PUBLISHED] === "true" ||
|
||||||
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] === "true")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasCancelingPublishPost ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -179,6 +194,7 @@ const handleOpenSettingModal = async (post: Post) => {
|
||||||
|
|
||||||
const onSettingModalClose = () => {
|
const onSettingModalClose = () => {
|
||||||
selectedPost.value = undefined;
|
selectedPost.value = undefined;
|
||||||
|
settingModal.value = false;
|
||||||
refetch();
|
refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -274,7 +290,7 @@ watch(selectedPostNames, (newValue) => {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PostSettingModal
|
<PostSettingModal
|
||||||
v-model:visible="settingModal"
|
v-if="settingModal"
|
||||||
:post="selectedPost"
|
:post="selectedPost"
|
||||||
@close="onSettingModalClose"
|
@close="onSettingModalClose"
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { usePermission } from "@/utils/permission";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { useQueryClient } from "@tanstack/vue-query";
|
import { useQueryClient } from "@tanstack/vue-query";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, toRefs, markRaw, ref, inject } from "vue";
|
import { computed, inject, markRaw, ref, toRefs } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { useEntityFieldItemExtensionPoint } from "@console/composables/use-entity-extension-points";
|
import { useEntityFieldItemExtensionPoint } from "@console/composables/use-entity-extension-points";
|
||||||
import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points";
|
import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points";
|
||||||
|
@ -25,6 +25,7 @@ import PublishStatusField from "./entity-fields/PublishStatusField.vue";
|
||||||
import VisibleField from "./entity-fields/VisibleField.vue";
|
import VisibleField from "./entity-fields/VisibleField.vue";
|
||||||
import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
|
import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
|
||||||
import PublishTimeField from "./entity-fields/PublishTimeField.vue";
|
import PublishTimeField from "./entity-fields/PublishTimeField.vue";
|
||||||
|
import { postLabels } from "@/constants/labels";
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
const { currentUserHasPermission } = usePermission();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -71,6 +72,26 @@ const { operationItems } = useOperationItemExtensionPoint<ListedPost>(
|
||||||
"post:list-item:operation:create",
|
"post:list-item:operation:create",
|
||||||
post,
|
post,
|
||||||
computed((): OperationItem<ListedPost>[] => [
|
computed((): OperationItem<ListedPost>[] => [
|
||||||
|
{
|
||||||
|
priority: 0,
|
||||||
|
component: markRaw(VDropdownItem),
|
||||||
|
label: t("core.common.buttons.publish"),
|
||||||
|
action: async () => {
|
||||||
|
await apiClient.post.publishPost({
|
||||||
|
name: props.post.post.metadata.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.publish_success"));
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["posts"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hidden:
|
||||||
|
props.post.post.metadata.labels?.[postLabels.PUBLISHED] == "true" ||
|
||||||
|
props.post.post.metadata.labels?.[postLabels.SCHEDULING_PUBLISH] ==
|
||||||
|
"true",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
priority: 10,
|
priority: 10,
|
||||||
component: markRaw(VDropdownItem),
|
component: markRaw(VDropdownItem),
|
||||||
|
@ -102,6 +123,29 @@ const { operationItems } = useOperationItemExtensionPoint<ListedPost>(
|
||||||
props: {
|
props: {
|
||||||
type: "danger",
|
type: "danger",
|
||||||
},
|
},
|
||||||
|
label: t("core.common.buttons.cancel_publish"),
|
||||||
|
action: async () => {
|
||||||
|
await apiClient.post.unpublishPost({
|
||||||
|
name: props.post.post.metadata.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.cancel_publish_success"));
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["posts"],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hidden:
|
||||||
|
props.post.post.metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
||||||
|
props.post.post.metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !==
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
priority: 50,
|
||||||
|
component: markRaw(VDropdownItem),
|
||||||
|
props: {
|
||||||
|
type: "danger",
|
||||||
|
},
|
||||||
label: t("core.common.buttons.delete"),
|
label: t("core.common.buttons.delete"),
|
||||||
permissions: [],
|
permissions: [],
|
||||||
action: handleDelete,
|
action: handleDelete,
|
||||||
|
|
|
@ -6,9 +6,8 @@ import {
|
||||||
VModal,
|
VModal,
|
||||||
VSpace,
|
VSpace,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { computed, nextTick, ref, toRaw, watch } from "vue";
|
import { computed, nextTick, ref, watch } from "vue";
|
||||||
import type { Post } from "@halo-dev/api-client";
|
import type { Post } from "@halo-dev/api-client";
|
||||||
import { cloneDeep } from "lodash-es";
|
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { useThemeCustomTemplates } from "@console/modules/interface/themes/composables/use-theme";
|
import { useThemeCustomTemplates } from "@console/modules/interface/themes/composables/use-theme";
|
||||||
import { postLabels } from "@/constants/labels";
|
import { postLabels } from "@/constants/labels";
|
||||||
|
@ -20,8 +19,31 @@ import useSlugify from "@console/composables/use-slugify";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { usePostUpdateMutate } from "../composables/use-post-update-mutate";
|
import { usePostUpdateMutate } from "../composables/use-post-update-mutate";
|
||||||
import { FormType } from "@/types/slug";
|
import { FormType } from "@/types/slug";
|
||||||
|
import { cloneDeep } from "lodash-es";
|
||||||
|
|
||||||
const initialFormState: Post = {
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
post?: Post;
|
||||||
|
publishSupport?: boolean;
|
||||||
|
onlyEmit?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
post: undefined,
|
||||||
|
publishSupport: true,
|
||||||
|
onlyEmit: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "close"): void;
|
||||||
|
(event: "saved", post: Post): void;
|
||||||
|
(event: "published", post: Post): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const modal = ref<InstanceType<typeof VModal>>();
|
||||||
|
const formState = ref<Post>({
|
||||||
spec: {
|
spec: {
|
||||||
title: "",
|
title: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
|
@ -47,34 +69,8 @@ const initialFormState: Post = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: randomUUID(),
|
name: randomUUID(),
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
const isSubmitting = ref(false);
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
visible: boolean;
|
|
||||||
post?: Post;
|
|
||||||
publishSupport?: boolean;
|
|
||||||
onlyEmit?: boolean;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
visible: false,
|
|
||||||
post: undefined,
|
|
||||||
publishSupport: true,
|
|
||||||
onlyEmit: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "update:visible", visible: boolean): void;
|
|
||||||
(event: "close"): void;
|
|
||||||
(event: "saved", post: Post): void;
|
|
||||||
(event: "published", post: Post): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const formState = ref<Post>(cloneDeep(initialFormState));
|
|
||||||
const saving = ref(false);
|
|
||||||
const publishing = ref(false);
|
const publishing = ref(false);
|
||||||
const publishCanceling = ref(false);
|
const publishCanceling = ref(false);
|
||||||
const submitType = ref<"publish" | "save">();
|
const submitType = ref<"publish" | "save">();
|
||||||
|
@ -84,13 +80,6 @@ const isUpdateMode = computed(() => {
|
||||||
return !!formState.value.metadata.creationTimestamp;
|
return !!formState.value.metadata.creationTimestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleVisibleChange = (visible: boolean) => {
|
|
||||||
emit("update:visible", visible);
|
|
||||||
if (!visible) {
|
|
||||||
emit("close");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (submitType.value === "publish") {
|
if (submitType.value === "publish") {
|
||||||
handlePublish();
|
handlePublish();
|
||||||
|
@ -139,11 +128,12 @@ const handleSave = async () => {
|
||||||
|
|
||||||
if (props.onlyEmit) {
|
if (props.onlyEmit) {
|
||||||
emit("saved", formState.value);
|
emit("saved", formState.value);
|
||||||
|
modal.value?.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saving.value = true;
|
isSubmitting.value = true;
|
||||||
|
|
||||||
const { data } = isUpdateMode.value
|
const { data } = isUpdateMode.value
|
||||||
? await postUpdateMutate(formState.value)
|
? await postUpdateMutate(formState.value)
|
||||||
|
@ -154,25 +144,28 @@ const handleSave = async () => {
|
||||||
formState.value = data;
|
formState.value = data;
|
||||||
emit("saved", data);
|
emit("saved", data);
|
||||||
|
|
||||||
handleVisibleChange(false);
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.save_success"));
|
Toast.success(t("core.common.toast.save_success"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to save post", e);
|
console.error("Failed to save post", e);
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
isSubmitting.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePublish = async () => {
|
const handlePublish = async () => {
|
||||||
if (props.onlyEmit) {
|
if (props.onlyEmit) {
|
||||||
emit("published", formState.value);
|
emit("published", formState.value);
|
||||||
|
modal.value?.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
publishing.value = true;
|
publishing.value = true;
|
||||||
|
|
||||||
|
await postUpdateMutate(formState.value);
|
||||||
|
|
||||||
const { data } = await apiClient.post.publishPost({
|
const { data } = await apiClient.post.publishPost({
|
||||||
name: formState.value.metadata.name,
|
name: formState.value.metadata.name,
|
||||||
});
|
});
|
||||||
|
@ -181,7 +174,7 @@ const handlePublish = async () => {
|
||||||
|
|
||||||
emit("published", data);
|
emit("published", data);
|
||||||
|
|
||||||
handleVisibleChange(false);
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.publish_success"));
|
Toast.success(t("core.common.toast.publish_success"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -199,7 +192,7 @@ const handleUnpublish = async () => {
|
||||||
name: formState.value.metadata.name,
|
name: formState.value.metadata.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
handleVisibleChange(false);
|
modal.value?.close();
|
||||||
|
|
||||||
Toast.success(t("core.common.toast.cancel_publish_success"));
|
Toast.success(t("core.common.toast.cancel_publish_success"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -214,7 +207,7 @@ watch(
|
||||||
() => props.post,
|
() => props.post,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
formState.value = toRaw(value);
|
formState.value = cloneDeep(value);
|
||||||
publishTime.value = toDatetimeLocal(formState.value.spec.publishTime);
|
publishTime.value = toDatetimeLocal(formState.value.spec.publishTime);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -264,14 +257,37 @@ const { handleGenerateSlug } = useSlugify(
|
||||||
computed(() => !isUpdateMode.value),
|
computed(() => !isUpdateMode.value),
|
||||||
FormType.POST
|
FormType.POST
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Buttons condition
|
||||||
|
const showPublishButton = computed(() => {
|
||||||
|
if (!props.publishSupport) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
[postLabels.PUBLISHED]: published,
|
||||||
|
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
|
||||||
|
} = formState.value.metadata.labels || {};
|
||||||
|
|
||||||
|
return published !== "true" && schedulingPublish !== "true";
|
||||||
|
});
|
||||||
|
|
||||||
|
const showCancelPublishButton = computed(() => {
|
||||||
|
const {
|
||||||
|
[postLabels.PUBLISHED]: published,
|
||||||
|
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
|
||||||
|
} = formState.value.metadata.labels || {};
|
||||||
|
|
||||||
|
return published === "true" || schedulingPublish === "true";
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<VModal
|
||||||
:visible="visible"
|
ref="modal"
|
||||||
:width="700"
|
:width="700"
|
||||||
:title="$t('core.post.settings.title')"
|
:title="$t('core.post.settings.title')"
|
||||||
:centered="false"
|
:centered="false"
|
||||||
@update:visible="handleVisibleChange"
|
@close="emit('close')"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<slot name="actions"></slot>
|
<slot name="actions"></slot>
|
||||||
|
@ -459,10 +475,10 @@ const { handleGenerateSlug } = useSlugify(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<template v-if="publishSupport">
|
|
||||||
<VButton
|
<VButton
|
||||||
v-if="formState.metadata.labels?.[postLabels.PUBLISHED] !== 'true'"
|
v-if="showPublishButton"
|
||||||
:loading="publishing"
|
:loading="publishing"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="handlePublishClick()"
|
@click="handlePublishClick()"
|
||||||
|
@ -474,21 +490,26 @@ const { handleGenerateSlug } = useSlugify(
|
||||||
}}
|
}}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-else
|
:loading="isSubmitting"
|
||||||
|
type="secondary"
|
||||||
|
@click="handleSaveClick()"
|
||||||
|
>
|
||||||
|
{{ $t("core.common.buttons.save") }}
|
||||||
|
</VButton>
|
||||||
|
<VButton type="default" @click="modal?.close()">
|
||||||
|
{{ $t("core.common.buttons.close") }}
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
|
|
||||||
|
<VButton
|
||||||
|
v-if="showCancelPublishButton"
|
||||||
:loading="publishCanceling"
|
:loading="publishCanceling"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleUnpublish()"
|
@click="handleUnpublish()"
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.cancel_publish") }}
|
{{ $t("core.common.buttons.cancel_publish") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
</template>
|
</div>
|
||||||
<VButton :loading="saving" type="secondary" @click="handleSaveClick()">
|
|
||||||
{{ $t("core.common.buttons.save") }}
|
|
||||||
</VButton>
|
|
||||||
<VButton type="default" @click="handleVisibleChange(false)">
|
|
||||||
{{ $t("core.common.buttons.close") }}
|
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -22,12 +22,11 @@ const publishStatus = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPublishing = computed(() => {
|
const isPublishing = computed(() => {
|
||||||
const { spec, status, metadata } = props.post.post;
|
const { spec, metadata } = props.post.post;
|
||||||
return (
|
return (
|
||||||
(spec.publish &&
|
spec.publish &&
|
||||||
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
||||||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,8 +15,7 @@ import {
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import PostListItem from "./components/PostListItem.vue";
|
import PostListItem from "./components/PostListItem.vue";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { computed } from "vue";
|
import { computed, watch } from "vue";
|
||||||
import { watch } from "vue";
|
|
||||||
import { postLabels } from "@/constants/labels";
|
import { postLabels } from "@/constants/labels";
|
||||||
|
|
||||||
const page = useRouteQuery<number>("page", 1, {
|
const page = useRouteQuery<number>("page", 1, {
|
||||||
|
@ -69,17 +68,34 @@ const {
|
||||||
size.value = data.size;
|
size.value = data.size;
|
||||||
},
|
},
|
||||||
refetchInterval: (data) => {
|
refetchInterval: (data) => {
|
||||||
const hasAbnormalPost = data?.items.some((post) => {
|
const hasDeletingPosts = data?.items.some((post) => post.post.spec.deleted);
|
||||||
const { spec, metadata, status } = post.post;
|
|
||||||
|
if (hasDeletingPosts) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPublishingPost = data?.items.some((post) => {
|
||||||
|
const { spec, metadata } = post.post;
|
||||||
return (
|
return (
|
||||||
spec.deleted ||
|
metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
|
||||||
(metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
|
||||||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
|
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasAbnormalPost ? 1000 : false;
|
if (hasPublishingPost) {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCancelingPublishPost = data?.items.some((post) => {
|
||||||
|
const { spec, metadata } = post.post;
|
||||||
|
return (
|
||||||
|
!spec.publish &&
|
||||||
|
(metadata.labels?.[postLabels.PUBLISHED] === "true" ||
|
||||||
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] === "true")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasCancelingPublishPost ? 1000 : false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
IconTimerLine,
|
IconTimerLine,
|
||||||
Toast,
|
Toast,
|
||||||
VAvatar,
|
VAvatar,
|
||||||
|
VDropdownDivider,
|
||||||
VDropdownItem,
|
VDropdownItem,
|
||||||
VEntity,
|
VEntity,
|
||||||
VEntityField,
|
VEntityField,
|
||||||
|
@ -49,13 +50,20 @@ const publishStatus = computed(() => {
|
||||||
: t("core.post.filters.status.items.draft");
|
: t("core.post.filters.status.items.draft");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPublished = computed(() => {
|
||||||
|
const {
|
||||||
|
[postLabels.PUBLISHED]: published,
|
||||||
|
[postLabels.SCHEDULING_PUBLISH]: schedulingPublish,
|
||||||
|
} = props.post.post.metadata.labels || {};
|
||||||
|
return published !== "true" && schedulingPublish !== "true";
|
||||||
|
});
|
||||||
|
|
||||||
const isPublishing = computed(() => {
|
const isPublishing = computed(() => {
|
||||||
const { spec, status, metadata } = props.post.post;
|
const { spec, metadata } = props.post.post;
|
||||||
return (
|
return (
|
||||||
(spec.publish &&
|
spec.publish &&
|
||||||
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
metadata.labels?.[postLabels.PUBLISHED] !== "true" &&
|
||||||
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true") ||
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
|
||||||
(spec.releaseSnapshot === spec.headSnapshot && status?.inProgress)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,6 +234,11 @@ function handleUnpublish() {
|
||||||
</VEntityField>
|
</VEntityField>
|
||||||
</template>
|
</template>
|
||||||
<template #dropdownItems>
|
<template #dropdownItems>
|
||||||
|
<HasPermission v-if="isPublished" :permissions="['uc:posts:publish']">
|
||||||
|
<VDropdownItem @click="handlePublish">
|
||||||
|
{{ $t("core.common.buttons.publish") }}
|
||||||
|
</VDropdownItem>
|
||||||
|
</HasPermission>
|
||||||
<VDropdownItem
|
<VDropdownItem
|
||||||
@click="
|
@click="
|
||||||
$router.push({
|
$router.push({
|
||||||
|
@ -236,14 +249,9 @@ function handleUnpublish() {
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.edit") }}
|
{{ $t("core.common.buttons.edit") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<HasPermission :permissions="['uc:posts:publish']">
|
<HasPermission v-if="!isPublished" :permissions="['uc:posts:publish']">
|
||||||
<VDropdownItem
|
<VDropdownDivider />
|
||||||
v-if="post.post.metadata.labels?.[postLabels.PUBLISHED] === 'false'"
|
<VDropdownItem type="danger" @click="handleUnpublish">
|
||||||
@click="handlePublish"
|
|
||||||
>
|
|
||||||
{{ $t("core.common.buttons.publish") }}
|
|
||||||
</VDropdownItem>
|
|
||||||
<VDropdownItem v-else type="danger" @click="handleUnpublish">
|
|
||||||
{{ $t("core.common.buttons.cancel_publish") }}
|
{{ $t("core.common.buttons.cancel_publish") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
|
|
Loading…
Reference in New Issue