From 8dd2df3a27f7a1b9c103c73203b9a607252387cb Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 2 Nov 2022 17:48:23 +0800 Subject: [PATCH] feat: support for managing recycle bin posts and single pages (halo-dev/console#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 支持管理已删除的文章和自定义页面,优化删除的逻辑。 Fixes https://github.com/halo-dev/halo/issues/2647 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. Halo 需要切换到 https://github.com/halo-dev/halo/pull/2648 2. 测试文章和自定义页面的删除功能,以及回收站的管理功能。 #### Does this PR introduce a user-facing change? ```release-note 支持管理已删除的文章和自定义页面,优化删除的逻辑。 ``` --- .../contents/pages/DeletedSinglePageList.vue | 413 +++++++++++++++++ src/modules/contents/pages/SinglePageList.vue | 104 ++++- .../contents/pages/layouts/PageLayout.vue | 26 +- src/modules/contents/pages/module.ts | 17 + .../contents/posts/DeletedPostList.vue | 421 ++++++++++++++++++ src/modules/contents/posts/PostList.vue | 35 +- src/modules/contents/posts/module.ts | 11 + 7 files changed, 1000 insertions(+), 27 deletions(-) create mode 100644 src/modules/contents/pages/DeletedSinglePageList.vue create mode 100644 src/modules/contents/posts/DeletedPostList.vue diff --git a/src/modules/contents/pages/DeletedSinglePageList.vue b/src/modules/contents/pages/DeletedSinglePageList.vue new file mode 100644 index 000000000..a802f30eb --- /dev/null +++ b/src/modules/contents/pages/DeletedSinglePageList.vue @@ -0,0 +1,413 @@ + + + diff --git a/src/modules/contents/pages/SinglePageList.vue b/src/modules/contents/pages/SinglePageList.vue index ab95da76b..3e17aeedf 100644 --- a/src/modules/contents/pages/SinglePageList.vue +++ b/src/modules/contents/pages/SinglePageList.vue @@ -22,7 +22,7 @@ import { } from "@halo-dev/components"; import SinglePageSettingModal from "./components/SinglePageSettingModal.vue"; import UserDropdownSelector from "@/components/dropdown-selector/UserDropdownSelector.vue"; -import { onMounted, ref, watchEffect } from "vue"; +import { onMounted, ref, watch, watchEffect } from "vue"; import type { ListedSinglePageList, SinglePage, @@ -57,7 +57,8 @@ const loading = ref(false); const settingModal = ref(false); const selectedSinglePage = ref(); const selectedSinglePageWithContent = ref(); -const checkAll = ref(false); +const selectedPageNames = ref([]); +const checkedAll = ref(false); const refreshInterval = ref(); const handleFetchSinglePages = async () => { @@ -73,6 +74,7 @@ const handleFetchSinglePages = async () => { } const { data } = await apiClient.singlePage.listSinglePages({ + labelSelector: [`content.halo.run/deleted=false`], page: singlePages.value.page, size: singlePages.value.size, visible: selectedVisibleItem.value.value, @@ -85,7 +87,7 @@ const handleFetchSinglePages = async () => { singlePages.value = data; const deletedSinglePages = singlePages.value.items.filter( - (singlePage) => !!singlePage.page.metadata.deletionTimestamp + (singlePage) => singlePage.page.spec.deleted ); if (deletedSinglePages.length) { @@ -192,9 +194,30 @@ const handleSelectNext = async () => { } }; +const checkSelection = (singlePage: SinglePage) => { + return ( + singlePage.metadata.name === selectedSinglePage.value?.metadata.name || + selectedPageNames.value.includes(singlePage.metadata.name) + ); +}; + +const handleCheckAllChange = (e: Event) => { + const { checked } = e.target as HTMLInputElement; + + if (checked) { + selectedPageNames.value = + singlePages.value.items.map((singlePage) => { + return singlePage.page.metadata.name; + }) || []; + } else { + selectedPageNames.value = []; + } +}; + const handleDelete = async (singlePage: SinglePage) => { Dialog.warning({ title: "是否确认删除该自定义页面?", + description: "此操作会将自定义页面放入回收站,后续可以从回收站恢复", confirmType: "danger", onConfirm: async () => { const singlePageToUpdate = cloneDeep(singlePage); @@ -210,6 +233,37 @@ const handleDelete = async (singlePage: SinglePage) => { }); }; +const handleDeleteInBatch = async () => { + Dialog.warning({ + title: "是否确认删除选中的自定义页面?", + description: "此操作会将自定义页面放入回收站,后续可以从回收站恢复", + confirmType: "danger", + onConfirm: async () => { + await Promise.all( + selectedPageNames.value.map((name) => { + const page = singlePages.value.items.find( + (item) => item.page.metadata.name === name + )?.page; + + if (!page) { + return Promise.resolve(); + } + + page.spec.deleted = true; + return apiClient.extension.singlePage.updatecontentHaloRunV1alpha1SinglePage( + { + name: page.metadata.name, + singlePage: page, + } + ); + }) + ); + await handleFetchSinglePages(); + selectedPageNames.value = []; + }, + }); +}; + const finalStatus = (singlePage: SinglePage) => { if (singlePage.status?.phase) { return SinglePagePhase[singlePage.status.phase]; @@ -217,6 +271,28 @@ const finalStatus = (singlePage: SinglePage) => { return ""; }; +watch(selectedPageNames, (newValue) => { + checkedAll.value = newValue.length === singlePages.value.items?.length; +}); + +watchEffect(async () => { + if ( + !selectedSinglePage.value || + !selectedSinglePage.value.spec.headSnapshot + ) { + return; + } + + const { data: content } = await apiClient.content.obtainSnapshotContent({ + snapshotName: selectedSinglePage.value.spec.headSnapshot, + }); + + selectedSinglePageWithContent.value = { + page: selectedSinglePage.value, + content: content, + }; +}); + onMounted(handleFetchSinglePages); // Filters @@ -351,15 +427,20 @@ function handleSortItemChange(sortItem?: SortItem) { class="mr-4 hidden items-center sm:flex" >
-
+
- 设置 - 删除 + 删除
@@ -561,13 +641,14 @@ function handleSortItemChange(sortItem?: SortItem) { role="list" >
  • - + + + +
    diff --git a/src/modules/contents/pages/module.ts b/src/modules/contents/pages/module.ts index 016f31b70..6ea57704a 100644 --- a/src/modules/contents/pages/module.ts +++ b/src/modules/contents/pages/module.ts @@ -4,6 +4,7 @@ import BlankLayout from "@/layouts/BlankLayout.vue"; import PageLayout from "./layouts/PageLayout.vue"; import FunctionalPageList from "./FunctionalPageList.vue"; import SinglePageList from "./SinglePageList.vue"; +import DeletedSinglePageList from "./DeletedSinglePageList.vue"; import SinglePageEditor from "./SinglePageEditor.vue"; import { IconPages } from "@halo-dev/components"; import { markRaw } from "vue"; @@ -63,6 +64,22 @@ export default definePlugin({ }, ], }, + { + path: "deleted", + component: BasicLayout, + children: [ + { + path: "", + name: "DeletedSinglePages", + component: DeletedSinglePageList, + meta: { + title: "自定义页面回收站", + searchable: true, + permissions: ["system:singlepages:view"], + }, + }, + ], + }, { path: "editor", component: BasicLayout, diff --git a/src/modules/contents/posts/DeletedPostList.vue b/src/modules/contents/posts/DeletedPostList.vue new file mode 100644 index 000000000..aea76693b --- /dev/null +++ b/src/modules/contents/posts/DeletedPostList.vue @@ -0,0 +1,421 @@ + + diff --git a/src/modules/contents/posts/PostList.vue b/src/modules/contents/posts/PostList.vue index d87aff768..feb673d39 100644 --- a/src/modules/contents/posts/PostList.vue +++ b/src/modules/contents/posts/PostList.vue @@ -40,6 +40,7 @@ import { usePostCategory } from "@/modules/contents/posts/categories/composables import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag"; import { usePermission } from "@/utils/permission"; import { onBeforeRouteLeave } from "vue-router"; +import cloneDeep from "lodash.clonedeep"; const { currentUserHasPermission } = usePermission(); @@ -93,6 +94,7 @@ const handleFetchPosts = async () => { } const { data } = await apiClient.post.listPosts({ + labelSelector: [`content.halo.run/deleted=false`], page: posts.value.page, size: posts.value.size, visible: selectedVisibleItem.value?.value, @@ -107,7 +109,7 @@ const handleFetchPosts = async () => { posts.value = data; const deletedPosts = posts.value.items.filter( - (post) => !!post.post.metadata.deletionTimestamp + (post) => post.post.spec.deleted ); if (deletedPosts.length) { @@ -217,17 +219,21 @@ const handleCheckAllChange = (e: Event) => { return post.post.metadata.name; }) || []; } else { - selectedPostNames.value.length = 0; + selectedPostNames.value = []; } }; const handleDelete = async (post: Post) => { Dialog.warning({ title: "是否确认删除该文章?", + description: "此操作会将文章放入回收站,后续可以从回收站恢复", confirmType: "danger", onConfirm: async () => { - await apiClient.extension.post.deletecontentHaloRunV1alpha1Post({ - name: post.metadata.name, + const postToUpdate = cloneDeep(post); + postToUpdate.spec.deleted = true; + await apiClient.extension.post.updatecontentHaloRunV1alpha1Post({ + name: postToUpdate.metadata.name, + post: postToUpdate, }); await handleFetchPosts(); }, @@ -237,17 +243,28 @@ const handleDelete = async (post: Post) => { const handleDeleteInBatch = async () => { Dialog.warning({ title: "是否确认删除选中的文章?", + description: "此操作会将文章放入回收站,后续可以从回收站恢复", confirmType: "danger", onConfirm: async () => { await Promise.all( selectedPostNames.value.map((name) => { - return apiClient.extension.post.deletecontentHaloRunV1alpha1Post({ - name, + const post = posts.value.items.find( + (item) => item.post.metadata.name === name + )?.post; + + if (!post) { + return Promise.resolve(); + } + + post.spec.deleted = true; + return apiClient.extension.post.updatecontentHaloRunV1alpha1Post({ + name: post.metadata.name, + post: post, }); }) ); await handleFetchPosts(); - selectedPostNames.value.length = 0; + selectedPostNames.value = []; }, }); }; @@ -418,6 +435,7 @@ function handleContributorChange(user?: User) { 分类 标签 + 回收站 - + diff --git a/src/modules/contents/posts/module.ts b/src/modules/contents/posts/module.ts index ca427a3cb..6254703b1 100644 --- a/src/modules/contents/posts/module.ts +++ b/src/modules/contents/posts/module.ts @@ -3,6 +3,7 @@ import BasicLayout from "@/layouts/BasicLayout.vue"; import BlankLayout from "@/layouts/BlankLayout.vue"; import { IconBookRead } from "@halo-dev/components"; import PostList from "./PostList.vue"; +import DeletedPostList from "./DeletedPostList.vue"; import PostEditor from "./PostEditor.vue"; import CategoryList from "./categories/CategoryList.vue"; import TagList from "./tags/TagList.vue"; @@ -33,6 +34,16 @@ export default definePlugin({ }, }, }, + { + path: "deleted", + name: "DeletedPosts", + component: DeletedPostList, + meta: { + title: "文章回收站", + searchable: true, + permissions: ["system:posts:view"], + }, + }, { path: "editor", name: "PostEditor",