halo/ui/console-src/modules/interface/menus/Menus.vue

274 lines
8.3 KiB
Vue
Raw Normal View History

<script lang="ts" setup>
import {
Dialog,
IconAddCircle,
IconListSettings,
Toast,
VButton,
VCard,
VEmpty,
VLoading,
VPageHeader,
VSpace,
} from "@halo-dev/components";
import MenuItemEditingModal from "./components/MenuItemEditingModal.vue";
import MenuItemListItem from "./components/MenuItemListItem.vue";
import MenuList from "./components/MenuList.vue";
import { computed, ref } from "vue";
import { apiClient } from "@/utils/api-client";
import type { Menu, MenuItem } from "@halo-dev/api-client";
import { cloneDeep } from "lodash-es";
import type { MenuTreeItem } from "./utils";
import {
buildMenuItemsTree,
convertMenuTreeItemToMenuItem,
convertTreeToMenuItems,
getChildrenNames,
resetMenuItemsTreePriority,
} from "./utils";
import { useDebounceFn } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import { useQuery, useQueryClient } from "@tanstack/vue-query";
const { t } = useI18n();
const queryClient = useQueryClient();
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
const selectedMenu = ref<Menu>();
const selectedMenuItem = ref<MenuItem>();
const selectedParentMenuItem = ref<MenuItem>();
const menuItemEditingModal = ref();
const {
data: menuItems,
isLoading,
refetch,
} = useQuery<MenuItem[]>({
queryKey: ["menu-items", selectedMenu],
queryFn: async () => {
if (!selectedMenu.value?.spec.menuItems) {
return [];
}
const menuItemNames = selectedMenu.value.spec.menuItems.filter(Boolean);
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: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> 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
const { data } = await apiClient.extension.menuItem.listv1alpha1MenuItem({
page: 0,
size: 0,
fieldSelector: [`name=(${menuItemNames.join(",")})`],
});
return data.items;
},
onSuccess(data) {
menuTreeItems.value = buildMenuItemsTree(data);
},
refetchInterval(data) {
const deletingMenuItems = data?.filter(
(menuItem) => !!menuItem.metadata.deletionTimestamp
);
return deletingMenuItems?.length ? 1000 : false;
},
enabled: computed(() => !!selectedMenu.value),
});
const handleOpenEditingModal = (menuItem: MenuTreeItem) => {
apiClient.extension.menuItem
.getv1alpha1MenuItem({
name: menuItem.metadata.name,
})
.then((response) => {
selectedMenuItem.value = response.data;
menuItemEditingModal.value = true;
});
};
const handleOpenCreateByParentModal = (menuItem: MenuTreeItem) => {
selectedParentMenuItem.value = convertMenuTreeItemToMenuItem(menuItem);
menuItemEditingModal.value = true;
};
const onMenuItemEditingModalClose = () => {
selectedParentMenuItem.value = undefined;
selectedMenuItem.value = undefined;
menuItemEditingModal.value = false;
};
const onMenuItemSaved = async (menuItem: MenuItem) => {
const menuToUpdate = cloneDeep(selectedMenu.value);
// update menu items
if (
menuToUpdate &&
!menuToUpdate.spec.menuItems?.includes(menuItem.metadata.name)
) {
menuToUpdate.spec.menuItems = [
...(menuToUpdate.spec.menuItems || []),
menuItem.metadata.name,
];
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: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> 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
await apiClient.extension.menu.updatev1alpha1Menu({
name: menuToUpdate.metadata.name,
menu: menuToUpdate,
});
}
await queryClient.invalidateQueries({ queryKey: ["menus"] });
await refetch();
};
const batchUpdating = ref(false);
const handleUpdateInBatch = useDebounceFn(async () => {
const menuTreeItemsToUpdate = resetMenuItemsTreePriority(menuTreeItems.value);
const menuItemsToUpdate = convertTreeToMenuItems(menuTreeItemsToUpdate);
try {
batchUpdating.value = true;
const promises = menuItemsToUpdate.map((menuItem) =>
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: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> 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
apiClient.extension.menuItem.updatev1alpha1MenuItem({
name: menuItem.metadata.name,
menuItem,
})
);
await Promise.all(promises);
} catch (e) {
console.error("Failed to update menu items", e);
} finally {
await queryClient.invalidateQueries({ queryKey: ["menus"] });
await refetch();
batchUpdating.value = false;
}
}, 300);
const handleDelete = async (menuItem: MenuTreeItem) => {
Dialog.info({
title: t("core.menu.operations.delete_menu_item.title"),
description: t("core.menu.operations.delete_menu_item.description"),
confirmType: "danger",
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
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: ![screenshot-before](https://user-images.githubusercontent.com/screenshot.png) After: ![screenshot-after](https://user-images.githubusercontent.com/screenshot.png) --> 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
await apiClient.extension.menuItem.deletev1alpha1MenuItem({
name: menuItem.metadata.name,
});
const childrenNames = getChildrenNames(menuItem);
if (childrenNames.length) {
const deleteChildrenRequests = childrenNames.map((name) =>
apiClient.extension.menuItem.deletev1alpha1MenuItem({
name,
})
);
await Promise.all(deleteChildrenRequests);
}
await refetch();
// update items under menu
const menuToUpdate = cloneDeep(selectedMenu.value);
if (menuToUpdate) {
menuToUpdate.spec.menuItems = menuToUpdate.spec.menuItems?.filter(
(name) => ![menuItem.metadata.name, ...childrenNames].includes(name)
);
await apiClient.extension.menu.updatev1alpha1Menu({
name: menuToUpdate.metadata.name,
menu: menuToUpdate,
});
}
await queryClient.invalidateQueries({ queryKey: ["menus"] });
Toast.success(t("core.common.toast.delete_success"));
},
});
};
</script>
<template>
<MenuItemEditingModal
v-if="menuItemEditingModal && selectedMenu"
:menu-item="selectedMenuItem"
:parent-menu-item="selectedParentMenuItem"
:menu="selectedMenu"
@close="onMenuItemEditingModalClose"
@saved="onMenuItemSaved"
/>
<VPageHeader :title="$t('core.menu.title')">
<template #icon>
<IconListSettings class="mr-2 self-center" />
</template>
</VPageHeader>
<div class="m-0 md:m-4">
<div class="flex flex-col gap-4 sm:flex-row">
<div class="w-96 flex-none">
<MenuList v-model:selected-menu="selectedMenu" />
</div>
<div class="flex-1">
<VCard :body-class="['!p-0']">
<template #header>
<div class="block w-full bg-gray-50 px-4 py-3">
<div
class="relative flex flex-col items-start sm:flex-row sm:items-center"
>
<div class="flex w-full flex-1 sm:w-auto">
<span class="text-base font-medium">
{{ selectedMenu?.spec.displayName }}
</span>
</div>
<div class="mt-4 flex sm:mt-0">
<VSpace>
<VButton
v-permission="['system:menus:manage']"
size="xs"
type="default"
@click="menuItemEditingModal = true"
>
{{ $t("core.common.buttons.new") }}
</VButton>
</VSpace>
</div>
</div>
</div>
</template>
<VLoading v-if="isLoading" />
<Transition v-else-if="!menuItems?.length" appear name="fade">
<VEmpty
:message="$t('core.menu.menu_item_empty.message')"
:title="$t('core.menu.menu_item_empty.title')"
>
<template #actions>
<VSpace>
<VButton @click="refetch()">
{{ $t("core.common.buttons.refresh") }}
</VButton>
<VButton
v-permission="['system:menus:manage']"
type="primary"
@click="menuItemEditingModal = true"
>
<template #icon>
<IconAddCircle class="h-full w-full" />
</template>
{{ $t("core.common.buttons.new") }}
</VButton>
</VSpace>
</template>
</VEmpty>
</Transition>
<Transition v-else appear name="fade">
<MenuItemListItem
v-model="menuTreeItems"
:class="{
'cursor-progress opacity-60': batchUpdating,
}"
@change="handleUpdateInBatch"
@delete="handleDelete"
@open-editing="handleOpenEditingModal"
@open-create-by-parent="handleOpenCreateByParentModal"
/>
</Transition>
</VCard>
</div>
</div>
</div>
</template>