mirror of https://github.com/halo-dev/halo
feat: add support for selecting parent menu item when creating menu item
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/3445/head
parent
2a12feccf1
commit
2ac0c525de
|
@ -28,8 +28,9 @@ import { useDebounceFn } from "@vueuse/core";
|
||||||
|
|
||||||
const menuItems = ref<MenuItem[]>([] as MenuItem[]);
|
const menuItems = ref<MenuItem[]>([] as MenuItem[]);
|
||||||
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
|
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
|
||||||
const selectedMenu = ref<Menu | undefined>();
|
const selectedMenu = ref<Menu>();
|
||||||
const selectedMenuItem = ref<MenuItem | null>(null);
|
const selectedMenuItem = ref<MenuItem>();
|
||||||
|
const selectedParentMenuItem = ref<MenuItem>();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const menuListRef = ref();
|
const menuListRef = ref();
|
||||||
const menuItemEditingModal = ref();
|
const menuItemEditingModal = ref();
|
||||||
|
@ -66,6 +67,16 @@ const handleOpenEditingModal = (menuItem: MenuTreeItem) => {
|
||||||
menuItemEditingModal.value = true;
|
menuItemEditingModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenCreateByParentModal = (menuItem: MenuTreeItem) => {
|
||||||
|
selectedParentMenuItem.value = convertMenuTreeItemToMenuItem(menuItem);
|
||||||
|
menuItemEditingModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMenuItemEditingModalClose = () => {
|
||||||
|
selectedParentMenuItem.value = undefined;
|
||||||
|
selectedMenuItem.value = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const onMenuItemSaved = async (menuItem: MenuItem) => {
|
const onMenuItemSaved = async (menuItem: MenuItem) => {
|
||||||
const menuToUpdate = cloneDeep(selectedMenu.value);
|
const menuToUpdate = cloneDeep(selectedMenu.value);
|
||||||
|
|
||||||
|
@ -143,7 +154,9 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
|
||||||
<MenuItemEditingModal
|
<MenuItemEditingModal
|
||||||
v-model:visible="menuItemEditingModal"
|
v-model:visible="menuItemEditingModal"
|
||||||
:menu-item="selectedMenuItem"
|
:menu-item="selectedMenuItem"
|
||||||
@close="selectedMenuItem = null"
|
:parent-menu-item="selectedParentMenuItem"
|
||||||
|
:menu="selectedMenu"
|
||||||
|
@close="onMenuItemEditingModalClose"
|
||||||
@saved="onMenuItemSaved"
|
@saved="onMenuItemSaved"
|
||||||
/>
|
/>
|
||||||
<VPageHeader title="菜单">
|
<VPageHeader title="菜单">
|
||||||
|
@ -214,6 +227,7 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
|
||||||
@change="handleUpdateInBatch"
|
@change="handleUpdateInBatch"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
@open-editing="handleOpenEditingModal"
|
@open-editing="handleOpenEditingModal"
|
||||||
|
@open-create-by-parent="handleOpenCreateByParentModal"
|
||||||
/>
|
/>
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { VButton, VModal, VSpace } from "@halo-dev/components";
|
import { VButton, VModal, VSpace } from "@halo-dev/components";
|
||||||
import SubmitButton from "@/components/button/SubmitButton.vue";
|
import SubmitButton from "@/components/button/SubmitButton.vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import type { MenuItem, Post, SinglePage } from "@halo-dev/api-client";
|
import type { Menu, MenuItem, Post, SinglePage } from "@halo-dev/api-client";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { reset } from "@formkit/core";
|
import { reset } from "@formkit/core";
|
||||||
|
@ -10,15 +10,20 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
||||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
||||||
import { setFocus } from "@/formkit/utils/focus";
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
import type { FormKitOptionsProp } from "@formkit/inputs";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
menuItem: MenuItem | null;
|
menu?: Menu;
|
||||||
|
parentMenuItem: MenuItem;
|
||||||
|
menuItem?: MenuItem;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
visible: false,
|
visible: false,
|
||||||
menuItem: null,
|
menu: undefined,
|
||||||
|
parentMenuItem: undefined,
|
||||||
|
menuItem: undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -42,6 +47,8 @@ const initialFormState: MenuItem = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const menuItemMap = ref<FormKitOptionsProp>();
|
||||||
|
const selectedParentMenuItem = ref<string>("");
|
||||||
const formState = ref<MenuItem>(cloneDeep(initialFormState));
|
const formState = ref<MenuItem>(cloneDeep(initialFormState));
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
|
||||||
|
@ -49,6 +56,25 @@ const isUpdateMode = computed(() => {
|
||||||
return !!formState.value.metadata.creationTimestamp;
|
return !!formState.value.metadata.creationTimestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleFetchMenuItems = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.extension.menuItem.listv1alpha1MenuItem({
|
||||||
|
fieldSelector: [`name=(${props.menu?.spec.menuItems?.join(",")})`],
|
||||||
|
});
|
||||||
|
menuItemMap.value = [
|
||||||
|
{ label: "无", value: undefined },
|
||||||
|
...data.items.map((menuItem) => {
|
||||||
|
return {
|
||||||
|
label: menuItem.status?.displayName as string,
|
||||||
|
value: menuItem.metadata.name,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Failed to fetch menu items", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveMenuItem = async () => {
|
const handleSaveMenuItem = async () => {
|
||||||
try {
|
try {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
|
@ -81,6 +107,25 @@ const handleSaveMenuItem = async () => {
|
||||||
await apiClient.extension.menuItem.createv1alpha1MenuItem({
|
await apiClient.extension.menuItem.createv1alpha1MenuItem({
|
||||||
menuItem: formState.value,
|
menuItem: formState.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if parent menu item is selected, add the new menu item to the parent menu item
|
||||||
|
if (selectedParentMenuItem.value) {
|
||||||
|
const { data: menuItemToUpdate } =
|
||||||
|
await apiClient.extension.menuItem.getv1alpha1MenuItem({
|
||||||
|
name: selectedParentMenuItem.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
menuItemToUpdate.spec.children = [
|
||||||
|
...(menuItemToUpdate.spec.children || []),
|
||||||
|
data.metadata.name,
|
||||||
|
];
|
||||||
|
|
||||||
|
await apiClient.extension.menuItem.updatev1alpha1MenuItem({
|
||||||
|
name: menuItemToUpdate.metadata.name,
|
||||||
|
menuItem: menuItemToUpdate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChange(false);
|
onVisibleChange(false);
|
||||||
emit("saved", data);
|
emit("saved", data);
|
||||||
}
|
}
|
||||||
|
@ -103,6 +148,7 @@ const handleResetForm = () => {
|
||||||
formState.value.metadata.name = uuid();
|
formState.value.metadata.name = uuid();
|
||||||
selectedMenuItemSource.value = menuItemSources[0].value;
|
selectedMenuItemSource.value = menuItemSources[0].value;
|
||||||
selectedRef.value = "";
|
selectedRef.value = "";
|
||||||
|
selectedParentMenuItem.value = "";
|
||||||
reset("menuitem-form");
|
reset("menuitem-form");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +156,7 @@ watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
selectedParentMenuItem.value = props.parentMenuItem?.metadata.name;
|
||||||
setFocus("displayNameInput");
|
setFocus("displayNameInput");
|
||||||
|
|
||||||
if (!props.menuItem) {
|
if (!props.menuItem) {
|
||||||
|
@ -287,6 +334,7 @@ watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
|
handleFetchMenuItems();
|
||||||
handleFetchCategories();
|
handleFetchCategories();
|
||||||
handleFetchTags();
|
handleFetchTags();
|
||||||
handleFetchPosts();
|
handleFetchPosts();
|
||||||
|
@ -310,6 +358,14 @@ watch(
|
||||||
:config="{ validationVisibility: 'submit' }"
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleSaveMenuItem"
|
@submit="handleSaveMenuItem"
|
||||||
>
|
>
|
||||||
|
<FormKit
|
||||||
|
v-if="!isUpdateMode && menuItemMap"
|
||||||
|
v-model="selectedParentMenuItem"
|
||||||
|
label="上级菜单项"
|
||||||
|
type="select"
|
||||||
|
:options="menuItemMap"
|
||||||
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="selectedMenuItemSource"
|
v-model="selectedMenuItemSource"
|
||||||
:options="menuItemSources"
|
:options="menuItemSources"
|
||||||
|
@ -317,8 +373,7 @@ watch(
|
||||||
label="类型"
|
label="类型"
|
||||||
type="select"
|
type="select"
|
||||||
@change="onMenuItemSourceChange"
|
@change="onMenuItemSourceChange"
|
||||||
>
|
/>
|
||||||
</FormKit>
|
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'custom'"
|
v-if="selectedMenuItemSource === 'custom'"
|
||||||
|
@ -328,7 +383,8 @@ watch(
|
||||||
type="text"
|
type="text"
|
||||||
name="displayName"
|
name="displayName"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'custom'"
|
v-if="selectedMenuItemSource === 'custom'"
|
||||||
v-model="formState.spec.href"
|
v-model="formState.spec.href"
|
||||||
|
@ -336,7 +392,7 @@ watch(
|
||||||
type="text"
|
type="text"
|
||||||
name="href"
|
name="href"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'post'"
|
v-if="selectedMenuItemSource === 'post'"
|
||||||
|
@ -345,7 +401,7 @@ watch(
|
||||||
type="select"
|
type="select"
|
||||||
:options="postMap"
|
:options="postMap"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'singlePage'"
|
v-if="selectedMenuItemSource === 'singlePage'"
|
||||||
|
@ -354,7 +410,7 @@ watch(
|
||||||
type="select"
|
type="select"
|
||||||
:options="singlePageMap"
|
:options="singlePageMap"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'tag'"
|
v-if="selectedMenuItemSource === 'tag'"
|
||||||
|
@ -363,7 +419,7 @@ watch(
|
||||||
type="select"
|
type="select"
|
||||||
:options="tagMap"
|
:options="tagMap"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'category'"
|
v-if="selectedMenuItemSource === 'category'"
|
||||||
|
@ -372,7 +428,7 @@ watch(
|
||||||
type="select"
|
type="select"
|
||||||
:options="categoryMap"
|
:options="categoryMap"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
/>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
|
|
|
@ -26,6 +26,7 @@ withDefaults(
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "change"): void;
|
(event: "change"): void;
|
||||||
(event: "open-editing", menuItem: MenuTreeItem): void;
|
(event: "open-editing", menuItem: MenuTreeItem): void;
|
||||||
|
(event: "open-create-by-parent", menuItem: MenuTreeItem): void;
|
||||||
(event: "delete", menuItem: MenuTreeItem): void;
|
(event: "delete", menuItem: MenuTreeItem): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -39,6 +40,10 @@ function onOpenEditingModal(menuItem: MenuTreeItem) {
|
||||||
emit("open-editing", menuItem);
|
emit("open-editing", menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onOpenCreateByParentModal(menuItem: MenuTreeItem) {
|
||||||
|
emit("open-create-by-parent", menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
function onDelete(menuItem: MenuTreeItem) {
|
function onDelete(menuItem: MenuTreeItem) {
|
||||||
emit("delete", menuItem);
|
emit("delete", menuItem);
|
||||||
}
|
}
|
||||||
|
@ -114,6 +119,14 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
|
||||||
>
|
>
|
||||||
修改
|
修改
|
||||||
</VButton>
|
</VButton>
|
||||||
|
<VButton
|
||||||
|
v-close-popper
|
||||||
|
block
|
||||||
|
type="default"
|
||||||
|
@click="onOpenCreateByParentModal(menuItem)"
|
||||||
|
>
|
||||||
|
添加子菜单项
|
||||||
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-close-popper
|
v-close-popper
|
||||||
block
|
block
|
||||||
|
@ -130,6 +143,7 @@ function getMenuItemRefDisplayName(menuItem: MenuTreeItem) {
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
@open-editing="onOpenEditingModal"
|
@open-editing="onOpenEditingModal"
|
||||||
|
@open-create-by-parent="onOpenCreateByParentModal"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue