mirror of https://github.com/halo-dev/halo
feat: add batch setting for partial post fields (#6142)
#### What type of PR is this? /kind feature /area ui /milestone 2.17.x #### What this PR does / why we need it: 支持批量为文章设置部分属性。  #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4631 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 支持批量为文章设置部分属性。 ```pull/6103/head
parent
5d5df7c7a9
commit
2ae5d222d9
|
@ -28,6 +28,7 @@ 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 PostBatchSettingModal from "./components/PostBatchSettingModal.vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -320,6 +321,23 @@ const handleCancelPublishInBatch = async () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 = [];
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => selectedPostNames.value,
|
() => selectedPostNames.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
@ -342,6 +360,11 @@ watch(
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</PostSettingModal>
|
</PostSettingModal>
|
||||||
|
<PostBatchSettingModal
|
||||||
|
v-if="batchSettingModalVisible"
|
||||||
|
:posts="batchSettingPosts"
|
||||||
|
@close="onBatchSettingModalClose"
|
||||||
|
/>
|
||||||
<VPageHeader :title="$t('core.post.title')">
|
<VPageHeader :title="$t('core.post.title')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconBookRead class="mr-2 self-center" />
|
<IconBookRead class="mr-2 self-center" />
|
||||||
|
@ -398,6 +421,9 @@ watch(
|
||||||
<VButton @click="handleCancelPublishInBatch">
|
<VButton @click="handleCancelPublishInBatch">
|
||||||
{{ $t("core.common.buttons.cancel_publish") }}
|
{{ $t("core.common.buttons.cancel_publish") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
|
<VButton @click="handleOpenBatchSettingModal">
|
||||||
|
{{ $t("core.post.operations.batch_setting.button") }}
|
||||||
|
</VButton>
|
||||||
<VButton type="danger" @click="handleDeleteInBatch">
|
<VButton type="danger" @click="handleDeleteInBatch">
|
||||||
{{ $t("core.common.buttons.delete") }}
|
{{ $t("core.common.buttons.delete") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||||
|
import {
|
||||||
|
coreApiClient,
|
||||||
|
type JsonPatchInner,
|
||||||
|
type ListedPost,
|
||||||
|
} from "@halo-dev/api-client";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
type ArrayPatchOp = "add" | "replace" | "removeAll";
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
category: {
|
||||||
|
enabled: boolean;
|
||||||
|
names?: string[];
|
||||||
|
op: ArrayPatchOp;
|
||||||
|
};
|
||||||
|
tag: {
|
||||||
|
enabled: boolean;
|
||||||
|
names?: string[];
|
||||||
|
op: ArrayPatchOp;
|
||||||
|
};
|
||||||
|
visible: {
|
||||||
|
enabled: boolean;
|
||||||
|
value: "PUBLIC" | "PRIVATE";
|
||||||
|
};
|
||||||
|
allowComment: {
|
||||||
|
enabled: boolean;
|
||||||
|
value: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{ posts: ListedPost[] }>(), {});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "close"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const modal = ref<InstanceType<typeof VModal> | null>(null);
|
||||||
|
|
||||||
|
const { mutate, isLoading } = useMutation({
|
||||||
|
mutationKey: ["batch-update-posts"],
|
||||||
|
mutationFn: async ({ data }: { data: FormData }) => {
|
||||||
|
for (const key in props.posts) {
|
||||||
|
const post = props.posts[key];
|
||||||
|
const jsonPatchInner: JsonPatchInner[] = [];
|
||||||
|
if (data.category.enabled) {
|
||||||
|
jsonPatchInner.push({
|
||||||
|
op: "add",
|
||||||
|
path: "/spec/categories",
|
||||||
|
value: computeArrayPatchValue(
|
||||||
|
data.category.op,
|
||||||
|
post.post.spec.categories || [],
|
||||||
|
data.category.names || []
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.tag.enabled) {
|
||||||
|
jsonPatchInner.push({
|
||||||
|
op: "add",
|
||||||
|
path: "/spec/tags",
|
||||||
|
value: computeArrayPatchValue(
|
||||||
|
data.tag.op,
|
||||||
|
post.post.spec.tags || [],
|
||||||
|
data.tag.names || []
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.visible.enabled) {
|
||||||
|
jsonPatchInner.push({
|
||||||
|
op: "add",
|
||||||
|
path: "/spec/visible",
|
||||||
|
value: data.visible.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.allowComment.enabled) {
|
||||||
|
jsonPatchInner.push({
|
||||||
|
op: "add",
|
||||||
|
path: "/spec/allowComment",
|
||||||
|
value: data.allowComment.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await coreApiClient.content.post.patchPost({
|
||||||
|
name: post.post.metadata.name,
|
||||||
|
jsonPatchInner,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.save_success"));
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["posts"] });
|
||||||
|
modal.value?.close();
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
Toast.error(t("core.common.toast.save_failed_and_retry"));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function computeArrayPatchValue(
|
||||||
|
op: ArrayPatchOp,
|
||||||
|
oldValue: string[],
|
||||||
|
newValue: string[]
|
||||||
|
) {
|
||||||
|
if (op === "add") {
|
||||||
|
return Array.from(new Set([...oldValue, ...newValue]));
|
||||||
|
} else if (op === "replace") {
|
||||||
|
return newValue;
|
||||||
|
} else if (op === "removeAll") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSubmit(data: FormData) {
|
||||||
|
mutate({ data });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VModal
|
||||||
|
ref="modal"
|
||||||
|
height="calc(100vh - 20px)"
|
||||||
|
:title="$t('core.post.batch_setting_modal.title')"
|
||||||
|
:width="700"
|
||||||
|
@close="emit('close')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
id="post-batch-settings-form"
|
||||||
|
type="form"
|
||||||
|
name="post-batch-settings-form"
|
||||||
|
@submit="onSubmit"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
v-slot="{ value }"
|
||||||
|
name="category"
|
||||||
|
type="group"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.category_group')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:value="false"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.enabled')"
|
||||||
|
type="checkbox"
|
||||||
|
name="enabled"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled"
|
||||||
|
type="select"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
value: 'add',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.add'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'replace',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.replace'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'removeAll',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.remove_all'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.op.label')"
|
||||||
|
name="op"
|
||||||
|
value="add"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled && value?.op !== 'removeAll'"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.category_names')"
|
||||||
|
type="categorySelect"
|
||||||
|
:multiple="true"
|
||||||
|
name="names"
|
||||||
|
validation="required"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-slot="{ value }"
|
||||||
|
type="group"
|
||||||
|
name="tag"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.tag_group')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:value="false"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.enabled')"
|
||||||
|
type="checkbox"
|
||||||
|
name="enabled"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled"
|
||||||
|
type="select"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
value: 'add',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.add'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'replace',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.replace'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'removeAll',
|
||||||
|
label: $t(
|
||||||
|
'core.post.batch_setting_modal.fields.common.op.options.remove_all'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.op.label')"
|
||||||
|
name="op"
|
||||||
|
value="add"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled && value?.op !== 'removeAll'"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.tag_names')"
|
||||||
|
type="tagSelect"
|
||||||
|
:multiple="true"
|
||||||
|
name="names"
|
||||||
|
validation="required"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-slot="{ value }"
|
||||||
|
type="group"
|
||||||
|
name="visible"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.visible_group')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:value="false"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.enabled')"
|
||||||
|
type="checkbox"
|
||||||
|
name="enabled"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled"
|
||||||
|
:options="[
|
||||||
|
{ label: $t('core.common.select.public'), value: 'PUBLIC' },
|
||||||
|
{
|
||||||
|
label: $t('core.common.select.private'),
|
||||||
|
value: 'PRIVATE',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.visible_value')"
|
||||||
|
name="value"
|
||||||
|
type="select"
|
||||||
|
value="PUBLIC"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-slot="{ value }"
|
||||||
|
type="group"
|
||||||
|
name="allowComment"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.allow_comment_group')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
:value="false"
|
||||||
|
:label="$t('core.post.batch_setting_modal.fields.common.enabled')"
|
||||||
|
type="checkbox"
|
||||||
|
name="enabled"
|
||||||
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="value?.enabled"
|
||||||
|
:options="[
|
||||||
|
{ label: $t('core.common.radio.yes'), value: true },
|
||||||
|
{ label: $t('core.common.radio.no'), value: false },
|
||||||
|
]"
|
||||||
|
:label="
|
||||||
|
$t('core.post.batch_setting_modal.fields.allow_comment_value')
|
||||||
|
"
|
||||||
|
name="value"
|
||||||
|
type="radio"
|
||||||
|
:value="true"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
</FormKit>
|
||||||
|
<template #footer>
|
||||||
|
<VSpace>
|
||||||
|
<VButton
|
||||||
|
type="secondary"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="$formkit.submit('post-batch-settings-form')"
|
||||||
|
>
|
||||||
|
{{ $t("core.common.buttons.save") }}
|
||||||
|
</VButton>
|
||||||
|
<VButton @click="modal?.close()">
|
||||||
|
{{ $t("core.common.buttons.cancel") }}
|
||||||
|
</VButton>
|
||||||
|
</VSpace>
|
||||||
|
</template>
|
||||||
|
</VModal>
|
||||||
|
</template>
|
|
@ -203,6 +203,8 @@ core:
|
||||||
description: >-
|
description: >-
|
||||||
Batch cancel publish posts, the selected posts will be set to
|
Batch cancel publish posts, the selected posts will be set to
|
||||||
unpublished status
|
unpublished status
|
||||||
|
batch_setting:
|
||||||
|
button: Batch settings
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -285,6 +287,25 @@ core:
|
||||||
create_time_asc: Earliest Created
|
create_time_asc: Earliest Created
|
||||||
display_name_desc: Descending order by tag name
|
display_name_desc: Descending order by tag name
|
||||||
display_name_asc: Ascending order by tag name
|
display_name_asc: Ascending order by tag name
|
||||||
|
batch_setting_modal:
|
||||||
|
title: Post batch settings
|
||||||
|
fields:
|
||||||
|
common:
|
||||||
|
enabled: Enabled
|
||||||
|
op:
|
||||||
|
label: Operate
|
||||||
|
options:
|
||||||
|
add: Add
|
||||||
|
replace: Replace
|
||||||
|
remove_all: Remove all
|
||||||
|
category_group: Category
|
||||||
|
category_names: Select categories
|
||||||
|
tag_group: Tag
|
||||||
|
tag_names: Select tags
|
||||||
|
visible_group: Visible
|
||||||
|
visible_value: "Select visible option "
|
||||||
|
allow_comment_group: " Allow comment"
|
||||||
|
allow_comment_value: Choose whether to allow comments
|
||||||
deleted_post:
|
deleted_post:
|
||||||
title: Deleted Posts
|
title: Deleted Posts
|
||||||
empty:
|
empty:
|
||||||
|
|
|
@ -191,6 +191,8 @@ core:
|
||||||
cancel_publish_in_batch:
|
cancel_publish_in_batch:
|
||||||
title: 取消发布文章
|
title: 取消发布文章
|
||||||
description: 批量取消发布文章,所选文章会被设置为未发布状态
|
description: 批量取消发布文章,所选文章会被设置为未发布状态
|
||||||
|
batch_setting:
|
||||||
|
button: 批量设置
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -273,6 +275,25 @@ core:
|
||||||
create_time_asc: 较早创建
|
create_time_asc: 较早创建
|
||||||
display_name_desc: 标签名降序
|
display_name_desc: 标签名降序
|
||||||
display_name_asc: 标签名升序
|
display_name_asc: 标签名升序
|
||||||
|
batch_setting_modal:
|
||||||
|
title: 文章批量设置
|
||||||
|
fields:
|
||||||
|
common:
|
||||||
|
enabled: 启用
|
||||||
|
op:
|
||||||
|
label: 设置方式
|
||||||
|
options:
|
||||||
|
add: 追加
|
||||||
|
replace: 替换
|
||||||
|
remove_all: 移除全部
|
||||||
|
category_group: 分类
|
||||||
|
category_names: 选择分类
|
||||||
|
tag_group: 标签
|
||||||
|
tag_names: 选择标签
|
||||||
|
visible_group: 可见性
|
||||||
|
visible_value: 选择可见性
|
||||||
|
allow_comment_group: 允许评论
|
||||||
|
allow_comment_value: 选择是否允许评论
|
||||||
deleted_post:
|
deleted_post:
|
||||||
title: 文章回收站
|
title: 文章回收站
|
||||||
empty:
|
empty:
|
||||||
|
|
|
@ -191,6 +191,8 @@ core:
|
||||||
cancel_publish_in_batch:
|
cancel_publish_in_batch:
|
||||||
title: 取消發佈文章
|
title: 取消發佈文章
|
||||||
description: 批量取消發佈文章,所選文章會被設置為未發布狀態
|
description: 批量取消發佈文章,所選文章會被設置為未發布狀態
|
||||||
|
batch_setting:
|
||||||
|
button: 批量設置
|
||||||
filters:
|
filters:
|
||||||
status:
|
status:
|
||||||
items:
|
items:
|
||||||
|
@ -265,6 +267,25 @@ core:
|
||||||
label: 自定義模板
|
label: 自定義模板
|
||||||
cover:
|
cover:
|
||||||
label: 封面圖
|
label: 封面圖
|
||||||
|
batch_setting_modal:
|
||||||
|
title: 文章批量設置
|
||||||
|
fields:
|
||||||
|
common:
|
||||||
|
enabled: 啟用
|
||||||
|
op:
|
||||||
|
label: 設置方式
|
||||||
|
options:
|
||||||
|
add: 追加
|
||||||
|
replace: 替换
|
||||||
|
remove_all: 移除全部
|
||||||
|
category_group: 分类
|
||||||
|
category_names: 選擇分類
|
||||||
|
tag_group: 標籤
|
||||||
|
tag_names: 選擇標籤
|
||||||
|
visible_group: 可見性
|
||||||
|
visible_value: 選擇可見性
|
||||||
|
allow_comment_group: 允許評論
|
||||||
|
allow_comment_value: 選擇是否允許評論
|
||||||
deleted_post:
|
deleted_post:
|
||||||
title: 文章回收站
|
title: 文章回收站
|
||||||
empty:
|
empty:
|
||||||
|
|
Loading…
Reference in New Issue