feat: add support for selecting the parent category when creating a new category (#4056)

#### What type of PR is this?

/area console
/kind feature
/milestone 2.7.x

#### What this PR does / why we need it:

支持在创建分类的时候选择上级分类。

<img width="805" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/02912f70-4de4-4b8e-bbbe-f973fbd3e684">

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/4040

#### Special notes for your reviewer:

测试方式:

1. 测试新建的时候指定上级分类的功能是否正常。
2. 点击某个分类的操作按钮,选择添加子分类的菜单项,检查在新建分类的表单中上级分类是否选中了此分类。

#### Does this PR introduce a user-facing change?

```release-note
Console 端的文章分类支持在新建时指定上级分类。
```
pull/4092/head^2
Ryan Wang 2023-06-19 10:54:11 +08:00 committed by GitHub
parent 4a1fe8dd1e
commit 2fd9cbde33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 93 additions and 18 deletions

View File

@ -276,6 +276,8 @@ core:
delete:
title: Are you sure you want to delete this category?
description: After deleting this category, the association with corresponding articles will be removed. This operation cannot be undone.
add_sub_category:
button: Add sub category
editing_modal:
titles:
update: Update post category
@ -284,6 +286,8 @@ core:
general: General
annotations: Annotations
fields:
parent:
label: Parent
display_name:
label: Display name
slug:

View File

@ -276,6 +276,8 @@ core:
delete:
title: 确定要删除该分类吗?
description: 删除此分类之后,对应文章的关联将被解除。该操作不可恢复。
add_sub_category:
button: 新增子分类
editing_modal:
titles:
update: 编辑文章分类
@ -284,6 +286,8 @@ core:
general: 常规
annotations: 元数据
fields:
parent:
label: 上级分类
display_name:
label: 名称
slug:

View File

@ -276,6 +276,8 @@ core:
delete:
title: 確定要刪除該分類嗎?
description: 刪除此分類之後,對應文章的關聯將被解除。該操作不可恢復。
add_sub_category:
button: 新增子分類
editing_modal:
titles:
update: 編輯文章分類
@ -284,6 +286,8 @@ core:
general: 常規
annotations: 元數據
fields:
parent:
label: 上級分類
display_name:
label: 名稱
slug:

View File

@ -33,7 +33,8 @@ import { useDebounceFn } from "@vueuse/core";
import { usePostCategory } from "./composables/use-post-category";
const editingModal = ref(false);
const selectedCategory = ref<Category | null>(null);
const selectedCategory = ref<Category>();
const selectedParentCategory = ref<Category>();
const {
categories,
@ -68,8 +69,14 @@ const handleOpenEditingModal = (category: CategoryTree) => {
editingModal.value = true;
};
const handleOpenCreateByParentModal = (category: CategoryTree) => {
selectedParentCategory.value = convertCategoryTreeToCategory(category);
editingModal.value = true;
};
const onEditingModalClose = () => {
selectedCategory.value = null;
selectedCategory.value = undefined;
selectedParentCategory.value = undefined;
handleFetchCategories();
};
</script>
@ -77,6 +84,7 @@ const onEditingModalClose = () => {
<CategoryEditingModal
v-model:visible="editingModal"
:category="selectedCategory"
:parent-category="selectedParentCategory"
@close="onEditingModalClose"
/>
<VPageHeader :title="$t('core.post_category.title')">
@ -147,6 +155,7 @@ const onEditingModalClose = () => {
@change="handleUpdateInBatch"
@delete="handleDelete"
@open-editing="handleOpenEditingModal"
@open-create-by-parent="handleOpenCreateByParentModal"
/>
</Transition>
</VCard>

View File

@ -28,11 +28,13 @@ import { useI18n } from "vue-i18n";
const props = withDefaults(
defineProps<{
visible: boolean;
category: Category | null;
category?: Category;
parentCategory?: Category;
}>(),
{
visible: false,
category: null,
category: undefined,
parentCategory: undefined,
}
);
@ -63,6 +65,7 @@ const initialFormState: Category = {
};
const formState = ref<Category>(cloneDeep(initialFormState));
const selectedParentCategory = ref("");
const saving = ref(false);
const isUpdateMode = computed(() => {
@ -100,9 +103,45 @@ const handleSaveCategory = async () => {
category: formState.value,
});
} else {
await apiClient.extension.category.createcontentHaloRunV1alpha1Category({
category: formState.value,
});
// Gets parent category, calculates priority and updates it.
let parentCategory: Category | undefined = undefined;
if (selectedParentCategory.value) {
const { data } =
await apiClient.extension.category.getcontentHaloRunV1alpha1Category({
name: selectedParentCategory.value,
});
parentCategory = data;
}
const priority = parentCategory?.spec.children
? parentCategory.spec.children.length + 1
: 0;
formState.value.spec.priority = priority;
const { data: createdCategory } =
await apiClient.extension.category.createcontentHaloRunV1alpha1Category(
{
category: formState.value,
}
);
if (parentCategory) {
parentCategory.spec.children = Array.from(
new Set([
...(parentCategory.spec.children || []),
createdCategory.metadata.name,
])
);
await apiClient.extension.category.updatecontentHaloRunV1alpha1Category(
{
name: selectedParentCategory.value,
category: parentCategory,
}
);
}
}
onVisibleChange(false);
@ -122,6 +161,7 @@ const onVisibleChange = (visible: boolean) => {
};
const handleResetForm = () => {
selectedParentCategory.value = "";
formState.value = cloneDeep(initialFormState);
reset("category-form");
};
@ -130,18 +170,15 @@ watch(
() => props.visible,
(visible) => {
if (visible) {
setFocus("displayNameInput");
} else {
handleResetForm();
}
}
);
if (props.parentCategory) {
selectedParentCategory.value = props.parentCategory.metadata.name;
}
watch(
() => props.category,
(category) => {
if (category) {
formState.value = cloneDeep(category);
if (props.category) {
formState.value = cloneDeep(props.category);
}
setFocus("displayNameInput");
} else {
handleResetForm();
}
@ -189,6 +226,14 @@ const { handleGenerateSlug } = useSlugify(
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<FormKit
v-if="!isUpdateMode"
v-model="selectedParentCategory"
type="categorySelect"
:label="
$t('core.post_category.editing_modal.fields.parent.label')
"
></FormKit>
<FormKit
id="displayNameInput"
v-model="formState.spec.displayName"

View File

@ -26,6 +26,7 @@ withDefaults(
const emit = defineEmits<{
(event: "change"): void;
(event: "open-editing", category: CategoryTree): void;
(event: "open-create-by-parent", category: CategoryTree): void;
(event: "delete", category: CategoryTree): void;
}>();
@ -39,6 +40,10 @@ function onOpenEditingModal(category: CategoryTree) {
emit("open-editing", category);
}
function onOpenCreateByParentModal(category: CategoryTree) {
emit("open-create-by-parent", category);
}
function onDelete(category: CategoryTree) {
emit("delete", category);
}
@ -117,6 +122,9 @@ function onDelete(category: CategoryTree) {
>
{{ $t("core.common.buttons.edit") }}
</VDropdownItem>
<VDropdownItem @click="onOpenCreateByParentModal(category)">
{{ $t("core.post_category.operations.add_sub_category.button") }}
</VDropdownItem>
<VDropdownItem
v-permission="['system:posts:manage']"
type="danger"
@ -132,6 +140,7 @@ function onDelete(category: CategoryTree) {
@change="onChange"
@delete="onDelete"
@open-editing="onOpenEditingModal"
@open-create-by-parent="onOpenCreateByParentModal"
/>
</li>
</template>