2022-05-12 07:28:21 +00:00
|
|
|
<script lang="ts" setup>
|
2024-06-26 10:42:50 +00:00
|
|
|
import CategoryFilterDropdown from "@/components/filter/CategoryFilterDropdown.vue";
|
|
|
|
import TagFilterDropdown from "@/components/filter/TagFilterDropdown.vue";
|
|
|
|
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
|
|
|
|
import { postLabels } from "@/constants/labels";
|
|
|
|
import type { ListedPost, Post } from "@halo-dev/api-client";
|
|
|
|
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
|
2022-05-17 08:45:30 +00:00
|
|
|
import {
|
2024-05-27 08:26:58 +00:00
|
|
|
Dialog,
|
2022-05-23 07:16:23 +00:00
|
|
|
IconAddCircle,
|
2022-08-23 04:08:10 +00:00
|
|
|
IconArrowLeft,
|
|
|
|
IconArrowRight,
|
2022-05-17 08:45:30 +00:00
|
|
|
IconBookRead,
|
2022-10-24 03:18:16 +00:00
|
|
|
IconRefreshLine,
|
2024-05-27 08:26:58 +00:00
|
|
|
Toast,
|
2022-06-14 07:56:55 +00:00
|
|
|
VButton,
|
|
|
|
VCard,
|
2022-08-23 04:08:10 +00:00
|
|
|
VEmpty,
|
2024-05-27 08:26:58 +00:00
|
|
|
VLoading,
|
2022-06-14 07:56:55 +00:00
|
|
|
VPageHeader,
|
2022-07-11 10:16:45 +00:00
|
|
|
VPagination,
|
2022-06-14 07:56:55 +00:00
|
|
|
VSpace,
|
|
|
|
} from "@halo-dev/components";
|
2024-06-26 10:42:50 +00:00
|
|
|
import { useQuery } from "@tanstack/vue-query";
|
|
|
|
import { useRouteQuery } from "@vueuse/router";
|
2024-05-27 08:26:58 +00:00
|
|
|
import type { Ref } from "vue";
|
|
|
|
import { computed, provide, ref, watch } from "vue";
|
2023-03-23 08:54:33 +00:00
|
|
|
import { useI18n } from "vue-i18n";
|
2024-06-26 09:58:50 +00:00
|
|
|
import PostBatchSettingModal from "./components/PostBatchSettingModal.vue";
|
2024-06-26 10:42:50 +00:00
|
|
|
import PostListItem from "./components/PostListItem.vue";
|
|
|
|
import PostSettingModal from "./components/PostSettingModal.vue";
|
2022-09-30 09:48:19 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
const { t } = useI18n();
|
2022-08-23 04:08:10 +00:00
|
|
|
|
|
|
|
const settingModal = ref(false);
|
2022-11-10 16:24:10 +00:00
|
|
|
const selectedPost = ref<Post>();
|
2022-08-23 04:08:10 +00:00
|
|
|
const checkedAll = ref(false);
|
|
|
|
const selectedPostNames = ref<string[]>([]);
|
2022-05-19 08:41:52 +00:00
|
|
|
|
2023-07-20 08:10:18 +00:00
|
|
|
provide<Ref<string[]>>("selectedPostNames", selectedPostNames);
|
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
// Filters
|
2023-07-12 06:17:20 +00:00
|
|
|
const page = useRouteQuery<number>("page", 1, {
|
|
|
|
transform: Number,
|
|
|
|
});
|
|
|
|
const size = useRouteQuery<number>("size", 20, {
|
|
|
|
transform: Number,
|
|
|
|
});
|
|
|
|
const selectedVisible = useRouteQuery<
|
|
|
|
"PUBLIC" | "INTERNAL" | "PRIVATE" | undefined
|
|
|
|
>("visible");
|
|
|
|
const selectedPublishStatus = useRouteQuery<string | undefined>("status");
|
|
|
|
const selectedSort = useRouteQuery<string | undefined>("sort");
|
|
|
|
const selectedCategory = useRouteQuery<string | undefined>("category");
|
|
|
|
const selectedTag = useRouteQuery<string | undefined>("tag");
|
|
|
|
const selectedContributor = useRouteQuery<string | undefined>("contributor");
|
|
|
|
const keyword = useRouteQuery<string>("keyword", "");
|
|
|
|
const total = ref(0);
|
|
|
|
const hasPrevious = ref(false);
|
|
|
|
const hasNext = ref(false);
|
2023-02-24 04:10:13 +00:00
|
|
|
|
2023-07-11 07:15:10 +00:00
|
|
|
watch(
|
|
|
|
() => [
|
|
|
|
selectedVisible.value,
|
|
|
|
selectedPublishStatus.value,
|
|
|
|
selectedSort.value,
|
|
|
|
selectedCategory.value,
|
|
|
|
selectedTag.value,
|
|
|
|
selectedContributor.value,
|
|
|
|
keyword.value,
|
|
|
|
],
|
|
|
|
() => {
|
|
|
|
page.value = 1;
|
2023-02-24 04:10:13 +00:00
|
|
|
}
|
2023-07-11 07:15:10 +00:00
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
|
|
|
|
function handleClearFilters() {
|
2023-07-11 07:15:10 +00:00
|
|
|
selectedVisible.value = undefined;
|
|
|
|
selectedPublishStatus.value = undefined;
|
|
|
|
selectedSort.value = undefined;
|
2023-02-24 04:10:13 +00:00
|
|
|
selectedCategory.value = undefined;
|
|
|
|
selectedTag.value = undefined;
|
|
|
|
selectedContributor.value = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasFilters = computed(() => {
|
|
|
|
return (
|
2023-07-11 07:15:10 +00:00
|
|
|
selectedVisible.value ||
|
|
|
|
selectedPublishStatus.value !== undefined ||
|
|
|
|
selectedSort.value ||
|
2023-02-24 04:10:13 +00:00
|
|
|
selectedCategory.value ||
|
|
|
|
selectedTag.value ||
|
2023-07-11 07:15:10 +00:00
|
|
|
selectedContributor.value
|
2023-02-24 04:10:13 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
const {
|
|
|
|
data: posts,
|
|
|
|
isLoading,
|
|
|
|
isFetching,
|
|
|
|
refetch,
|
|
|
|
} = useQuery<ListedPost[]>({
|
|
|
|
queryKey: [
|
|
|
|
"posts",
|
|
|
|
page,
|
|
|
|
size,
|
|
|
|
selectedCategory,
|
|
|
|
selectedTag,
|
|
|
|
selectedContributor,
|
2023-07-11 07:15:10 +00:00
|
|
|
selectedPublishStatus,
|
|
|
|
selectedVisible,
|
|
|
|
selectedSort,
|
2023-02-24 04:10:13 +00:00
|
|
|
keyword,
|
|
|
|
],
|
|
|
|
queryFn: async () => {
|
2022-11-10 16:24:10 +00:00
|
|
|
const labelSelector: string[] = ["content.halo.run/deleted=false"];
|
2024-01-25 04:17:12 +00:00
|
|
|
const fieldSelector: string[] = [];
|
2022-09-09 16:32:46 +00:00
|
|
|
|
2022-09-23 03:18:12 +00:00
|
|
|
if (selectedCategory.value) {
|
2024-01-25 04:17:12 +00:00
|
|
|
fieldSelector.push(`spec.categories=${selectedCategory.value}`);
|
2022-09-09 16:32:46 +00:00
|
|
|
}
|
|
|
|
|
2022-09-23 03:18:12 +00:00
|
|
|
if (selectedTag.value) {
|
2024-01-25 04:17:12 +00:00
|
|
|
fieldSelector.push(`spec.tags=${selectedTag.value}`);
|
2022-09-09 16:32:46 +00:00
|
|
|
}
|
|
|
|
|
2022-09-23 03:18:12 +00:00
|
|
|
if (selectedContributor.value) {
|
2024-01-25 04:17:12 +00:00
|
|
|
fieldSelector.push(`status.contributors=${selectedContributor.value}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selectedVisible.value) {
|
|
|
|
fieldSelector.push(`spec.visible=${selectedVisible.value}`);
|
2022-09-09 16:32:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-11 07:15:10 +00:00
|
|
|
if (selectedPublishStatus.value !== undefined) {
|
2024-05-24 04:58:51 +00:00
|
|
|
labelSelector.push(selectedPublishStatus.value);
|
2022-11-10 16:24:10 +00:00
|
|
|
}
|
|
|
|
|
2024-06-25 04:31:44 +00:00
|
|
|
const { data } = await consoleApiClient.content.post.listPosts({
|
2022-11-10 16:24:10 +00:00
|
|
|
labelSelector,
|
2024-01-25 04:17:12 +00:00
|
|
|
fieldSelector,
|
2023-02-24 04:10:13 +00:00
|
|
|
page: page.value,
|
|
|
|
size: size.value,
|
2023-07-12 06:17:20 +00:00
|
|
|
sort: [selectedSort.value].filter(Boolean) as string[],
|
2022-09-23 03:18:12 +00:00
|
|
|
keyword: keyword.value,
|
refactor: method parameters of api client (halo-dev/console#605)
<!-- 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 improvement
/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:
修改 api-client 的请求参数结构,改为所有参数由一个对象包裹,而不是将各个参数作为方法的参数,防止因为后端参数结构发生改变,或者生成 api-client 时参数顺序发生改变导致请求异常。如:
```diff
await apiClient.extension.storage.group.updatestorageHaloRunV1alpha1Group(
- formState.value.metadata.name,
- formState.value
+ {
+ name: formState.value.metadata.name,
+ group: formState.value,
+ }
);
```
#### Which issue(s) this PR fixes:
<!--
PR 合并时自动关闭 issue。
Automatically closes linked issue when PR is merged.
用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)`
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
None
#### Screenshots:
<!--
如果此 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:

-->
None
#### Special notes for your reviewer:
/cc @halo-dev/sig-halo-admin
#### 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
```
2022-09-06 02:26:11 +00:00
|
|
|
});
|
2022-10-24 14:10:10 +00:00
|
|
|
|
2023-02-24 04:10:13 +00:00
|
|
|
total.value = data.total;
|
|
|
|
hasNext.value = data.hasNext;
|
|
|
|
hasPrevious.value = data.hasPrevious;
|
|
|
|
|
|
|
|
return data.items;
|
|
|
|
},
|
|
|
|
refetchInterval: (data) => {
|
2024-05-27 08:26:58 +00:00
|
|
|
const hasDeletingPost = data?.some((post) => post.post.spec.deleted);
|
|
|
|
|
|
|
|
if (hasDeletingPost) {
|
|
|
|
return 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasPublishingPost = data?.some((post) => {
|
|
|
|
const { spec, metadata } = post.post;
|
|
|
|
return (
|
|
|
|
metadata.labels?.[postLabels.PUBLISHED] !== spec.publish + "" &&
|
|
|
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] !== "true"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (hasPublishingPost) {
|
|
|
|
return 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
const hasCancelingPublishPost = data?.some((post) => {
|
|
|
|
const { spec, metadata } = post.post;
|
2022-11-10 16:24:10 +00:00
|
|
|
return (
|
2024-05-27 08:26:58 +00:00
|
|
|
!spec.publish &&
|
|
|
|
(metadata.labels?.[postLabels.PUBLISHED] === "true" ||
|
|
|
|
metadata.labels?.[postLabels.SCHEDULING_PUBLISH] === "true")
|
2022-11-10 16:24:10 +00:00
|
|
|
);
|
|
|
|
});
|
2022-10-24 14:10:10 +00:00
|
|
|
|
2024-05-27 08:26:58 +00:00
|
|
|
return hasCancelingPublishPost ? 1000 : false;
|
2023-02-24 04:10:13 +00:00
|
|
|
},
|
2022-10-24 14:10:10 +00:00
|
|
|
});
|
|
|
|
|
2022-08-23 09:10:00 +00:00
|
|
|
const handleOpenSettingModal = async (post: Post) => {
|
2024-06-25 04:31:44 +00:00
|
|
|
const { data } = await coreApiClient.content.post.getPost({
|
|
|
|
name: post.metadata.name,
|
|
|
|
});
|
2022-08-23 09:10:00 +00:00
|
|
|
selectedPost.value = data;
|
2022-08-23 04:08:10 +00:00
|
|
|
settingModal.value = true;
|
2022-05-12 07:28:21 +00:00
|
|
|
};
|
2022-05-17 08:45:30 +00:00
|
|
|
|
2022-08-23 04:08:10 +00:00
|
|
|
const onSettingModalClose = () => {
|
2022-11-10 16:24:10 +00:00
|
|
|
selectedPost.value = undefined;
|
2024-05-27 08:26:58 +00:00
|
|
|
settingModal.value = false;
|
2023-02-24 04:10:13 +00:00
|
|
|
refetch();
|
2022-08-23 04:08:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleSelectPrevious = async () => {
|
2023-02-24 04:10:13 +00:00
|
|
|
if (!posts.value) return;
|
|
|
|
|
|
|
|
const index = posts.value.findIndex(
|
2022-08-23 04:08:10 +00:00
|
|
|
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
|
2022-08-23 04:08:10 +00:00
|
|
|
if (index > 0) {
|
2024-06-25 04:31:44 +00:00
|
|
|
const { data: previousPost } = await coreApiClient.content.post.getPost({
|
|
|
|
name: posts.value[index - 1].post.metadata.name,
|
|
|
|
});
|
2023-02-24 04:10:13 +00:00
|
|
|
selectedPost.value = previousPost;
|
2022-08-23 04:08:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-03-26 08:50:07 +00:00
|
|
|
if (index === 0 && hasPrevious.value) {
|
2023-02-24 04:10:13 +00:00
|
|
|
page.value--;
|
|
|
|
await refetch();
|
|
|
|
selectedPost.value = posts.value[posts.value.length - 1].post;
|
2022-08-23 04:08:10 +00:00
|
|
|
}
|
2022-05-17 08:45:30 +00:00
|
|
|
};
|
|
|
|
|
2022-08-23 04:08:10 +00:00
|
|
|
const handleSelectNext = async () => {
|
2023-02-24 04:10:13 +00:00
|
|
|
if (!posts.value) return;
|
|
|
|
|
|
|
|
const index = posts.value.findIndex(
|
2022-08-23 04:08:10 +00:00
|
|
|
(post) => post.post.metadata.name === selectedPost.value?.metadata.name
|
2022-07-06 06:31:42 +00:00
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
if (index < posts.value.length - 1) {
|
2024-06-25 04:31:44 +00:00
|
|
|
const { data: nextPost } = await coreApiClient.content.post.getPost({
|
|
|
|
name: posts.value[index + 1].post.metadata.name,
|
|
|
|
});
|
2023-02-24 04:10:13 +00:00
|
|
|
selectedPost.value = nextPost;
|
2022-08-23 04:08:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2023-02-24 04:10:13 +00:00
|
|
|
if (index === posts.value.length - 1 && hasNext) {
|
|
|
|
page.value++;
|
|
|
|
await refetch();
|
|
|
|
selectedPost.value = posts.value[0].post;
|
2022-05-17 08:45:30 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-08-23 04:08:10 +00:00
|
|
|
const checkSelection = (post: Post) => {
|
|
|
|
return (
|
|
|
|
post.metadata.name === selectedPost.value?.metadata.name ||
|
|
|
|
selectedPostNames.value.includes(post.metadata.name)
|
2022-07-06 06:31:42 +00:00
|
|
|
);
|
2022-08-23 04:08:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleCheckAllChange = (e: Event) => {
|
|
|
|
const { checked } = e.target as HTMLInputElement;
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
selectedPostNames.value =
|
2023-02-24 04:10:13 +00:00
|
|
|
posts.value?.map((post) => {
|
2022-08-23 04:08:10 +00:00
|
|
|
return post.post.metadata.name;
|
|
|
|
}) || [];
|
|
|
|
} else {
|
2022-11-02 09:48:23 +00:00
|
|
|
selectedPostNames.value = [];
|
2022-08-23 04:08:10 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-09-19 12:36:15 +00:00
|
|
|
const handleDeleteInBatch = async () => {
|
2022-10-18 01:58:09 +00:00
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.post.operations.delete_in_batch.title"),
|
|
|
|
description: t("core.post.operations.delete_in_batch.description"),
|
2022-09-19 12:36:15 +00:00
|
|
|
confirmType: "danger",
|
2023-03-23 08:54:33 +00:00
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-09-19 12:36:15 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
await Promise.all(
|
|
|
|
selectedPostNames.value.map((name) => {
|
2024-06-25 04:31:44 +00:00
|
|
|
return consoleApiClient.content.post.recyclePost({
|
2023-02-24 04:10:13 +00:00
|
|
|
name,
|
2022-09-19 12:36:15 +00:00
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2023-02-24 04:10:13 +00:00
|
|
|
await refetch();
|
2022-11-02 09:48:23 +00:00
|
|
|
selectedPostNames.value = [];
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
2022-09-19 12:36:15 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-06-26 03:38:48 +00:00
|
|
|
const handlePublishInBatch = async () => {
|
|
|
|
Dialog.info({
|
|
|
|
title: t("core.post.operations.publish_in_batch.title"),
|
|
|
|
description: t("core.post.operations.publish_in_batch.description"),
|
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
|
|
|
onConfirm: async () => {
|
|
|
|
for (const i in selectedPostNames.value) {
|
|
|
|
const name = selectedPostNames.value[i];
|
|
|
|
await consoleApiClient.content.post.publishPost({ name });
|
|
|
|
}
|
|
|
|
|
|
|
|
await refetch();
|
|
|
|
selectedPostNames.value = [];
|
|
|
|
|
|
|
|
Toast.success(t("core.common.toast.publish_success"));
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCancelPublishInBatch = async () => {
|
|
|
|
Dialog.warning({
|
|
|
|
title: t("core.post.operations.cancel_publish_in_batch.title"),
|
|
|
|
description: t("core.post.operations.cancel_publish_in_batch.description"),
|
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
|
|
|
onConfirm: async () => {
|
|
|
|
for (const i in selectedPostNames.value) {
|
|
|
|
const name = selectedPostNames.value[i];
|
|
|
|
await consoleApiClient.content.post.unpublishPost({ name });
|
|
|
|
}
|
|
|
|
|
|
|
|
await refetch();
|
|
|
|
selectedPostNames.value = [];
|
|
|
|
|
|
|
|
Toast.success(t("core.common.toast.cancel_publish_success"));
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-06-26 09:58:50 +00:00
|
|
|
// Batch settings
|
|
|
|
const batchSettingModalVisible = ref(false);
|
|
|
|
const batchSettingPosts = ref<ListedPost[]>([]);
|
|
|
|
|
|
|
|
function handleOpenBatchSettingModal() {
|
|
|
|
batchSettingPosts.value = selectedPostNames.value.map((name) => {
|
|
|
|
return posts.value?.find((post) => post.post.metadata.name === name);
|
|
|
|
}) as ListedPost[];
|
|
|
|
|
|
|
|
batchSettingModalVisible.value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBatchSettingModalClose() {
|
|
|
|
batchSettingModalVisible.value = false;
|
|
|
|
batchSettingPosts.value = [];
|
|
|
|
}
|
|
|
|
|
2024-06-26 03:38:48 +00:00
|
|
|
watch(
|
|
|
|
() => selectedPostNames.value,
|
|
|
|
(newValue) => {
|
|
|
|
checkedAll.value = newValue.length === posts.value?.length;
|
|
|
|
}
|
|
|
|
);
|
2022-05-12 07:28:21 +00:00
|
|
|
</script>
|
|
|
|
<template>
|
2022-07-06 06:31:42 +00:00
|
|
|
<PostSettingModal
|
2024-05-27 08:26:58 +00:00
|
|
|
v-if="settingModal"
|
2022-11-10 16:24:10 +00:00
|
|
|
:post="selectedPost"
|
2022-08-23 04:08:10 +00:00
|
|
|
@close="onSettingModalClose"
|
|
|
|
>
|
|
|
|
<template #actions>
|
2022-09-11 09:10:25 +00:00
|
|
|
<span @click="handleSelectPrevious">
|
2023-03-23 08:54:33 +00:00
|
|
|
<IconArrowLeft v-tooltip="$t('core.common.buttons.previous')" />
|
2022-09-11 09:10:25 +00:00
|
|
|
</span>
|
|
|
|
<span @click="handleSelectNext">
|
2023-03-23 08:54:33 +00:00
|
|
|
<IconArrowRight v-tooltip="$t('core.common.buttons.next')" />
|
2022-09-11 09:10:25 +00:00
|
|
|
</span>
|
2022-08-23 04:08:10 +00:00
|
|
|
</template>
|
|
|
|
</PostSettingModal>
|
2024-06-26 09:58:50 +00:00
|
|
|
<PostBatchSettingModal
|
|
|
|
v-if="batchSettingModalVisible"
|
|
|
|
:posts="batchSettingPosts"
|
|
|
|
@close="onBatchSettingModalClose"
|
|
|
|
/>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VPageHeader :title="$t('core.post.title')">
|
2022-05-12 07:28:21 +00:00
|
|
|
<template #icon>
|
2022-05-29 15:55:06 +00:00
|
|
|
<IconBookRead class="mr-2 self-center" />
|
2022-05-12 07:28:21 +00:00
|
|
|
</template>
|
|
|
|
<template #actions>
|
2022-05-30 14:38:57 +00:00
|
|
|
<VSpace>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VButton :route="{ name: 'Categories' }" size="sm">
|
|
|
|
{{ $t("core.post.actions.categories") }}
|
|
|
|
</VButton>
|
|
|
|
<VButton :route="{ name: 'Tags' }" size="sm">
|
|
|
|
{{ $t("core.post.actions.tags") }}
|
|
|
|
</VButton>
|
|
|
|
<VButton :route="{ name: 'DeletedPosts' }" size="sm">
|
|
|
|
{{ $t("core.post.actions.recycle_bin") }}
|
|
|
|
</VButton>
|
2022-12-22 04:14:29 +00:00
|
|
|
|
2022-09-30 09:48:19 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:posts:manage']"
|
|
|
|
:route="{ name: 'PostEditor' }"
|
|
|
|
type="secondary"
|
|
|
|
>
|
2022-05-30 14:38:57 +00:00
|
|
|
<template #icon>
|
|
|
|
<IconAddCircle class="h-full w-full" />
|
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.new") }}
|
2022-05-30 14:38:57 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
2022-05-12 07:28:21 +00:00
|
|
|
</template>
|
|
|
|
</VPageHeader>
|
|
|
|
|
|
|
|
<div class="m-0 md:m-4">
|
|
|
|
<VCard :body-class="['!p-0']">
|
|
|
|
<template #header>
|
2022-05-29 15:55:06 +00:00
|
|
|
<div class="block w-full bg-gray-50 px-4 py-3">
|
2022-05-19 08:41:52 +00:00
|
|
|
<div
|
2023-09-14 16:14:14 +00:00
|
|
|
class="relative flex flex-col flex-wrap items-start gap-4 sm:flex-row sm:items-center"
|
2022-05-19 08:41:52 +00:00
|
|
|
>
|
2022-09-30 09:48:19 +00:00
|
|
|
<div
|
|
|
|
v-permission="['system:posts:manage']"
|
2023-09-14 16:14:14 +00:00
|
|
|
class="hidden items-center sm:flex"
|
2022-09-30 09:48:19 +00:00
|
|
|
>
|
2022-05-19 08:41:52 +00:00
|
|
|
<input
|
2022-08-23 04:08:10 +00:00
|
|
|
v-model="checkedAll"
|
2022-05-19 08:41:52 +00:00
|
|
|
type="checkbox"
|
2022-08-23 04:08:10 +00:00
|
|
|
@change="handleCheckAllChange"
|
2022-05-19 08:41:52 +00:00
|
|
|
/>
|
|
|
|
</div>
|
2022-09-09 16:32:46 +00:00
|
|
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
2023-07-11 07:15:10 +00:00
|
|
|
<SearchInput v-if="!selectedPostNames.length" v-model="keyword" />
|
2022-05-19 08:41:52 +00:00
|
|
|
<VSpace v-else>
|
2024-06-26 03:38:48 +00:00
|
|
|
<VButton @click="handlePublishInBatch">
|
|
|
|
{{ $t("core.common.buttons.publish") }}
|
|
|
|
</VButton>
|
|
|
|
<VButton @click="handleCancelPublishInBatch">
|
|
|
|
{{ $t("core.common.buttons.cancel_publish") }}
|
|
|
|
</VButton>
|
2024-06-26 09:58:50 +00:00
|
|
|
<VButton @click="handleOpenBatchSettingModal">
|
|
|
|
{{ $t("core.post.operations.batch_setting.button") }}
|
|
|
|
</VButton>
|
2022-09-19 12:36:15 +00:00
|
|
|
<VButton type="danger" @click="handleDeleteInBatch">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.delete") }}
|
2022-09-19 12:36:15 +00:00
|
|
|
</VButton>
|
2022-05-19 08:41:52 +00:00
|
|
|
</VSpace>
|
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
<VSpace spacing="lg" class="flex-wrap">
|
|
|
|
<FilterCleanButton
|
|
|
|
v-if="hasFilters"
|
|
|
|
@click="handleClearFilters"
|
|
|
|
/>
|
|
|
|
<FilterDropdown
|
|
|
|
v-model="selectedPublishStatus"
|
|
|
|
:label="$t('core.common.filters.labels.status')"
|
|
|
|
:items="[
|
|
|
|
{
|
|
|
|
label: t('core.common.filters.item_labels.all'),
|
|
|
|
value: undefined,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.status.items.published'),
|
2024-05-24 04:58:51 +00:00
|
|
|
value: `${postLabels.PUBLISHED}=true`,
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.status.items.draft'),
|
2024-05-24 04:58:51 +00:00
|
|
|
value: `${postLabels.PUBLISHED}=false`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.status.items.scheduling'),
|
|
|
|
value: `${postLabels.SCHEDULING_PUBLISH}=true`,
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
]"
|
|
|
|
/>
|
|
|
|
<FilterDropdown
|
|
|
|
v-model="selectedVisible"
|
|
|
|
:label="$t('core.post.filters.visible.label')"
|
|
|
|
:items="[
|
|
|
|
{
|
|
|
|
label: t('core.common.filters.item_labels.all'),
|
|
|
|
value: undefined,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.visible.items.public'),
|
|
|
|
value: 'PUBLIC',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.visible.items.private'),
|
|
|
|
value: 'PRIVATE',
|
|
|
|
},
|
|
|
|
]"
|
|
|
|
/>
|
|
|
|
<CategoryFilterDropdown
|
|
|
|
v-model="selectedCategory"
|
|
|
|
:label="$t('core.post.filters.category.label')"
|
|
|
|
/>
|
|
|
|
<TagFilterDropdown
|
|
|
|
v-model="selectedTag"
|
|
|
|
:label="$t('core.post.filters.tag.label')"
|
|
|
|
/>
|
2023-09-22 08:11:53 +00:00
|
|
|
<HasPermission :permissions="['system:users:view']">
|
|
|
|
<UserFilterDropdown
|
|
|
|
v-model="selectedContributor"
|
|
|
|
:label="$t('core.post.filters.author.label')"
|
|
|
|
/>
|
|
|
|
</HasPermission>
|
2023-09-14 16:14:14 +00:00
|
|
|
<FilterDropdown
|
|
|
|
v-model="selectedSort"
|
|
|
|
:label="$t('core.common.filters.labels.sort')"
|
|
|
|
:items="[
|
|
|
|
{
|
|
|
|
label: t('core.common.filters.item_labels.default'),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.publish_time_desc'),
|
2024-01-25 04:17:12 +00:00
|
|
|
value: 'spec.publishTime,desc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.publish_time_asc'),
|
2024-01-25 04:17:12 +00:00
|
|
|
value: 'spec.publishTime,asc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.create_time_desc'),
|
2024-01-25 04:17:12 +00:00
|
|
|
value: 'metadata.creationTimestamp,desc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.create_time_asc'),
|
2024-01-25 04:17:12 +00:00
|
|
|
value: 'metadata.creationTimestamp,asc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
2024-04-23 03:28:53 +00:00
|
|
|
{
|
|
|
|
label: t(
|
|
|
|
'core.post.filters.sort.items.last_modify_time_desc'
|
|
|
|
),
|
|
|
|
value: 'status.lastModifyTime,desc',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t(
|
|
|
|
'core.post.filters.sort.items.last_modify_time_asc'
|
|
|
|
),
|
|
|
|
value: 'status.lastModifyTime,asc',
|
|
|
|
},
|
2024-04-11 10:10:11 +00:00
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.visit_desc'),
|
|
|
|
value: 'stats.visit,desc',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.post.filters.sort.items.comment_desc'),
|
|
|
|
value: 'stats.totalComment,desc',
|
|
|
|
},
|
2023-09-14 16:14:14 +00:00
|
|
|
]"
|
|
|
|
/>
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
<div
|
|
|
|
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
|
|
|
@click="refetch()"
|
|
|
|
>
|
|
|
|
<IconRefreshLine
|
|
|
|
v-tooltip="$t('core.common.buttons.refresh')"
|
|
|
|
:class="{ 'animate-spin text-gray-900': isFetching }"
|
|
|
|
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
|
|
|
/>
|
2022-10-24 03:18:16 +00:00
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
</div>
|
|
|
|
</VSpace>
|
2022-05-12 07:28:21 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
2023-02-24 04:10:13 +00:00
|
|
|
<VLoading v-if="isLoading" />
|
|
|
|
<Transition v-else-if="!posts?.length" appear name="fade">
|
2023-03-23 08:54:33 +00:00
|
|
|
<VEmpty
|
|
|
|
:message="$t('core.post.empty.message')"
|
|
|
|
:title="$t('core.post.empty.title')"
|
|
|
|
>
|
2022-11-24 03:46:10 +00:00
|
|
|
<template #actions>
|
|
|
|
<VSpace>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VButton @click="refetch">
|
|
|
|
{{ $t("core.common.buttons.refresh") }}
|
|
|
|
</VButton>
|
2022-11-24 03:46:10 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:posts:manage']"
|
|
|
|
:route="{ name: 'PostEditor' }"
|
2024-09-26 08:47:31 +00:00
|
|
|
type="secondary"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
|
|
|
<template #icon>
|
|
|
|
<IconAddCircle class="h-full w-full" />
|
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.new") }}
|
2022-11-24 03:46:10 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
|
|
|
</template>
|
|
|
|
</VEmpty>
|
|
|
|
</Transition>
|
|
|
|
<Transition v-else appear name="fade">
|
|
|
|
<ul
|
|
|
|
class="box-border h-full w-full divide-y divide-gray-100"
|
|
|
|
role="list"
|
|
|
|
>
|
2023-09-01 03:02:12 +00:00
|
|
|
<li v-for="post in posts" :key="post.post.metadata.name">
|
2023-07-20 08:10:18 +00:00
|
|
|
<PostListItem
|
|
|
|
:post="post"
|
|
|
|
:is-selected="checkSelection(post.post)"
|
|
|
|
@open-setting-modal="handleOpenSettingModal"
|
|
|
|
/>
|
2022-11-24 03:46:10 +00:00
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</Transition>
|
2022-05-12 07:28:21 +00:00
|
|
|
|
|
|
|
<template #footer>
|
2023-07-28 03:15:08 +00:00
|
|
|
<VPagination
|
|
|
|
v-model:page="page"
|
|
|
|
v-model:size="size"
|
|
|
|
:page-label="$t('core.components.pagination.page_label')"
|
|
|
|
:size-label="$t('core.components.pagination.size_label')"
|
|
|
|
:total-label="
|
|
|
|
$t('core.components.pagination.total_label', { total: total })
|
|
|
|
"
|
|
|
|
:total="total"
|
|
|
|
:size-options="[20, 30, 50, 100]"
|
|
|
|
/>
|
2022-05-12 07:28:21 +00:00
|
|
|
</template>
|
|
|
|
</VCard>
|
|
|
|
</div>
|
|
|
|
</template>
|