Browse Source

Merge pull request #1303 from colinin/enhance-favorite-menu

feat(vben5-platform): Enhance favorite menu
pull/1312/head
yx lin 8 months ago
committed by GitHub
parent
commit
c8c3d1ea9c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      apps/vben5/apps/app-antd/package.json
  2. 8
      apps/vben5/apps/app-antd/src/adapter/component/index.ts
  3. 1
      apps/vben5/packages/@abp/platform/package.json
  4. 34
      apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts
  5. 106
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue
  6. 137
      apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue
  7. 29
      apps/vben5/packages/@abp/platform/src/components/workbench/index.vue
  8. 9
      apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json
  9. 9
      apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json
  10. 1
      apps/vben5/pnpm-workspace.yaml

3
apps/vben5/apps/app-antd/package.json

@ -66,6 +66,7 @@
"dayjs": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
"vue-router": "catalog:",
"vue3-colorpicker": "catalog:"
}
}

8
apps/vben5/apps/app-antd/src/adapter/component/index.ts

@ -31,6 +31,12 @@ const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const ColorPicker = defineAsyncComponent(() =>
import('vue3-colorpicker').then((res) => {
import('vue3-colorpicker/style.css');
return res.ColorPicker;
}),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
@ -117,6 +123,7 @@ export type ComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'ColorPicker'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
@ -182,6 +189,7 @@ async function initComponentAdapter() {
AutoComplete,
Checkbox,
CheckboxGroup,
ColorPicker,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {

1
apps/vben5/packages/@abp/platform/package.json

@ -39,6 +39,7 @@
"ant-design-vue": "catalog:",
"lodash.clonedeep": "catalog:",
"vue": "catalog:*",
"vue3-colorpicker": "catalog:",
"vxe-table": "catalog:"
},
"devDependencies": {

34
apps/vben5/packages/@abp/platform/src/api/useMyFavoriteMenusApi.ts

@ -1,12 +1,42 @@
import type { ListResultDto } from '@abp/core';
import type { UserFavoriteMenuDto } from '../types/favorites';
import type {
UserFavoriteMenuCreateDto,
UserFavoriteMenuDto,
} from '../types/favorites';
import { useRequest } from '@abp/request';
export function useMyFavoriteMenusApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns
*/
function createApi(
input: UserFavoriteMenuCreateDto,
): Promise<UserFavoriteMenuDto> {
return request<UserFavoriteMenuDto>(
`/api/platform/menus/favorites/my-favorite-menus`,
{
data: input,
method: 'POST',
},
);
}
/**
*
* @param id Id
*/
function deleteApi(id: string): Promise<void> {
return request(`/api/platform/menus/favorites/my-favorite-menus/${id}`, {
method: 'DELETE',
});
}
/**
*
* @param framework ui框架
@ -25,6 +55,8 @@ export function useMyFavoriteMenusApi() {
return {
cancel,
createApi,
deleteApi,
getListApi,
};
}

106
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNav.vue

@ -1,6 +1,10 @@
<script setup lang="ts">
import type { FavoriteMenu } from '../types';
import { computed, h } from 'vue';
import { $t } from '@vben/locales';
import {
Card,
CardContent,
@ -9,6 +13,9 @@ import {
VbenIcon,
} from '@vben-core/shadcn-ui';
import { DeleteOutlined } from '@ant-design/icons-vue';
import { Dropdown, Menu, Modal } from 'ant-design-vue';
interface Props {
items?: FavoriteMenu[];
title: string;
@ -18,13 +25,53 @@ defineOptions({
name: 'WorkbenchQuickNav',
});
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
items: () => [],
});
defineEmits<{
const emits = defineEmits<{
(event: 'click', menu: FavoriteMenu): void;
(event: 'delete', menu: FavoriteMenu): void;
(event: 'add'): void;
}>();
const MenuItem = Menu.Item;
const getFavoriteMenus = computed(() => {
const addMenu: FavoriteMenu = {
id: 'addMenu',
displayName: $t('workbench.content.favoriteMenu.create'),
icon: 'ion:add-outline',
color: '#00bfff',
isDefault: true,
};
return [...props.items, addMenu];
});
function onClick(menu: FavoriteMenu) {
if (menu.id === 'addMenu') {
emits('add');
return;
}
emits('click', menu);
}
function onMenuClick(key: string, menu: FavoriteMenu) {
switch (key) {
case 'delete': {
Modal.confirm({
centered: true,
iconType: 'warning',
title: $t('AbpUi.AreYouSure'),
content: $t('AbpUi.ItemWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
emits('delete', menu);
},
});
}
}
}
</script>
<template>
@ -33,25 +80,42 @@ defineEmits<{
<CardTitle class="text-lg">{{ title }}</CardTitle>
</CardHeader>
<CardContent class="flex flex-wrap p-0">
<template v-for="(item, index) in items" :key="item.displayName">
<div
:class="{
'border-r-0': index % 3 === 2,
'border-b-0': index < 3,
'pb-4': index > 2,
'rounded-bl-xl': index === items.length - 3,
'rounded-br-xl': index === items.length - 1,
}"
class="flex-col-center border-border group w-1/3 cursor-pointer border-r border-t py-8 hover:shadow-xl"
>
<VbenIcon
:color="item.color"
:icon="item.icon"
class="size-7 transition-all duration-300 group-hover:scale-125"
@click="$emit('click', item)"
/>
<span class="text-md mt-2 truncate">{{ item.displayName }}</span>
</div>
<template
v-for="(item, index) in getFavoriteMenus"
:key="item.displayName"
>
<Dropdown :trigger="['contextmenu']">
<div
:class="{
'border-r-0': index % 3 === 2,
'border-b-0': index < 3,
'pb-4': index > 2,
'rounded-bl-xl': index === items.length - 3,
'rounded-br-xl': index === items.length - 1,
}"
class="flex-col-center border-border group w-1/3 cursor-pointer border-r border-t py-8 hover:shadow-xl"
@click="onClick(item)"
>
<VbenIcon
:color="item.color"
:icon="item.icon"
class="size-7 transition-all duration-300 group-hover:scale-125"
/>
<span class="text-md mt-2 truncate">{{ item.displayName }}</span>
</div>
<template #overlay>
<Menu
v-if="!item.isDefault"
@click="
({ key: menuKey }) => onMenuClick(menuKey.toString(), item)
"
>
<MenuItem key="delete" :icon="h(DeleteOutlined)">
{{ $t('workbench.content.favoriteMenu.delete') }}
</MenuItem>
</Menu>
</template>
</Dropdown>
</template>
</CardContent>
</Card>

137
apps/vben5/packages/@abp/platform/src/components/workbench/components/WorkbenchQuickNavModal.vue

@ -0,0 +1,137 @@
<script setup lang="ts">
import type { MenuDto, UserFavoriteMenuDto } from '../../../types';
import { defineAsyncComponent, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { useAppConfig } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { listToTree } from '@abp/core';
import { message, TreeSelect } from 'ant-design-vue';
import { useMyFavoriteMenusApi } from '../../../api/useMyFavoriteMenusApi';
import { useMyMenusApi } from '../../../api/useMyMenusApi';
const emits = defineEmits<{
(event: 'change', data: UserFavoriteMenuDto): void;
}>();
const ColorPicker = defineAsyncComponent(() =>
import('vue3-colorpicker').then((res) => {
import('vue3-colorpicker/style.css');
return res.ColorPicker;
}),
);
const availableMenus = ref<MenuDto[]>([]);
const { getAllApi } = useMyMenusApi();
const { createApi } = useMyFavoriteMenusApi();
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD);
const [Form, formApi] = useVbenForm({
schema: [
{
label: $t('workbench.content.favoriteMenu.select'),
fieldName: 'menuId',
component: 'TreeSelect',
rules: 'selectRequired',
},
{
label: $t('workbench.content.favoriteMenu.color'),
fieldName: 'color',
component: 'ColorPicker',
defaultValue: '#000000',
modelPropName: 'pureColor',
},
{
label: $t('workbench.content.favoriteMenu.alias'),
fieldName: 'aliasName',
component: 'Input',
},
{
label: $t('workbench.content.favoriteMenu.icon'),
fieldName: 'icon',
component: 'IconPicker',
},
],
showDefaultActions: false,
handleSubmit: onSubmit,
commonConfig: {
colon: true,
componentProps: {
class: 'w-full',
},
},
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen) {
if (isOpen) {
await onInitMenus();
}
},
});
async function onInitMenus() {
const { items } = await getAllApi({
framework: uiFramework,
});
const menus = listToTree<MenuDto>(items, { id: 'id', pid: 'parentId' });
availableMenus.value = menus;
}
async function onSubmit(values: Record<string, any>) {
try {
modalApi.setState({ submitting: true });
const menuDto = await createApi({
framework: uiFramework,
menuId: values.menuId,
color: values.color,
icon: values.icon,
aliasName: values.aliasName,
});
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', menuDto);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('workbench.content.favoriteMenu.manage')">
<Form>
<template #color="slotProps">
<div class="flex flex-row items-center">
<ColorPicker v-bind="slotProps" format="hex" />
<span>({{ slotProps.value }})</span>
</div>
</template>
<template #menuId="slotProps">
<TreeSelect
allow-clear
class="w-full"
tree-icon
v-bind="slotProps"
:field-names="{ label: 'displayName', value: 'id' }"
:tree-data="availableMenus"
>
<template #title="item">
<div class="flex flex-row items-center gap-1">
<IconifyIcon v-if="item.meta?.icon" :icon="item.meta.icon" />
<span>{{ item.displayName }}</span>
</div>
</template>
</TreeSelect>
</template>
</Form>
</Modal>
</template>
<style scoped></style>

29
apps/vben5/packages/@abp/platform/src/components/workbench/index.vue

@ -3,8 +3,9 @@ import type { WorkbenchTodoItem, WorkbenchTrendItem } from '@vben/common-ui';
import type { FavoriteMenu } from './types';
import { computed, onMounted, ref } from 'vue';
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
@ -16,7 +17,7 @@ import {
useMyNotifilersApi,
useNotificationSerializer,
} from '@abp/notifications';
import { Empty } from 'ant-design-vue';
import { Empty, message } from 'ant-design-vue';
import { useMyFavoriteMenusApi } from '../../api/useMyFavoriteMenusApi';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
@ -30,7 +31,8 @@ defineEmits<{
const userStore = useUserStore();
const { getMyNotifilersApi } = useMyNotifilersApi();
const { getListApi: getFavoriteMenusApi } = useMyFavoriteMenusApi();
const { getListApi: getFavoriteMenusApi, deleteApi: deleteFavoriteMenuApi } =
useMyFavoriteMenusApi();
const { deserialize } = useNotificationSerializer();
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD);
@ -107,6 +109,13 @@ const getWelcomeTitle = computed(() => {
}
return '';
});
const [WorkbenchQuickNavModal, quickNavModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/WorkbenchQuickNavModal.vue'),
),
});
async function onInit() {
await Promise.all([
onInitFavoriteMenus(),
@ -119,6 +128,7 @@ async function onInitFavoriteMenus() {
favoriteMenus.value = items.map((item) => {
return {
...item,
id: item.menuId,
isDefault: false,
};
});
@ -144,6 +154,16 @@ async function onInitTodoList() {
todoList.value = [];
}
function onCreatingFavoriteMenu() {
quickNavModalApi.open();
}
async function onDeleteFavoriteMenu(menu: FavoriteMenu) {
await deleteFavoriteMenuApi(menu.id);
await onInitFavoriteMenus();
message.success($t('AbpUi.SuccessfullyDeleted'));
}
onMounted(onInit);
</script>
@ -166,6 +186,8 @@ onMounted(onInit);
:items="getFavoriteMenus"
class="mt-5 lg:mt-0"
:title="$t('workbench.content.favoriteMenu.title')"
@add="onCreatingFavoriteMenu"
@delete="onDeleteFavoriteMenu"
@click="(menu: FavoriteMenu) => $emit('navTo', menu)"
/>
<WorkbenchTodo
@ -189,6 +211,7 @@ onMounted(onInit);
</WorkbenchTrends>
</div>
</div>
<WorkbenchQuickNavModal @change="onInitFavoriteMenus" />
</div>
</template>

9
apps/vben5/packages/@abp/platform/src/locales/langs/en-US/workbench.json

@ -18,7 +18,14 @@
"dashboard": "Dashboard",
"profile": "Personal Profile",
"settings": "Personal Settings",
"notifiers": "Notifiers"
"notifiers": "Notifiers",
"manage": "Manage menu",
"create": "New menu",
"delete": "Delete Menu",
"select": "Select Menu",
"color": "Select Color",
"alias": "Alias Name",
"icon": "Icon"
},
"trends": {
"title": "Latest News"

9
apps/vben5/packages/@abp/platform/src/locales/langs/zh-CN/workbench.json

@ -18,7 +18,14 @@
"dashboard": "仪表盘",
"profile": "个人中心",
"settings": "个人设置",
"notifiers": "通知消息"
"notifiers": "通知消息",
"manage": "管理菜单",
"create": "添加菜单",
"delete": "删除菜单",
"select": "选择菜单",
"color": "选择颜色",
"alias": "自定义别名",
"icon": "自定义图标"
},
"trends": {
"title": "最新消息"

1
apps/vben5/pnpm-workspace.yaml

@ -205,6 +205,7 @@ catalog:
vue-simple-uploader: ^1.0.3
vue-tippy: ^6.7.0
vue-tsc: 2.2.10
vue3-colorpicker: ^2.3.0
vxe-pc-ui: ^4.7.12
vxe-table: ^4.14.4
watermark-js-plus: ^1.6.0

Loading…
Cancel
Save