Browse Source

添加收藏菜单响应事件.

pull/700/head
cKey 3 years ago
parent
commit
d739be3a55
  1. 4
      apps/vue/src/api/model/baseModel.ts
  2. 114
      apps/vue/src/api/platform/user-favorites-menu/index.ts
  3. 28
      apps/vue/src/api/platform/user-favorites-menu/model/index.ts
  4. 6
      apps/vue/src/locales/lang/en/routes/dashboard.ts
  5. 8
      apps/vue/src/locales/lang/zh-CN/routes/dashboard.ts
  6. 71
      apps/vue/src/views/dashboard/workbench/components/MenuCard.vue
  7. 75
      apps/vue/src/views/dashboard/workbench/components/MenuReference.vue
  8. 49
      apps/vue/src/views/dashboard/workbench/components/menuProps.ts
  9. 93
      apps/vue/src/views/dashboard/workbench/index.vue

4
apps/vue/src/api/model/baseModel.ts

@ -51,6 +51,10 @@ export class CreationAuditedEntityDto implements IMayHaveCreator {
creationTime!: Date;
}
export class EntityDto<TKey> {
id!: TKey;
}
/** 实体审计对象 */
export class AuditedEntityDto implements CreationAuditedEntityDto, IModificationAuditedObject {
lastModifierId: string | undefined;

114
apps/vue/src/api/platform/user-favorites-menu/index.ts

@ -0,0 +1,114 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { UserFavoriteMenuDto, UserFavoriteMenuCreateDto, UserFavoriteMenuUpdateDto } from './model';
const remoteService = {
name: 'Platform',
controller: 'UserFavoriteMenu',
replace: {
framework: 'Vue Vben Admin',
}
};
export const create = (userId: string, input: UserFavoriteMenuCreateDto) => {
input.framework = remoteService.replace.framework;
return defAbpHttp.request<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'CreateAsync',
params: {
userId: userId,
},
data: {
input: input,
},
});
};
export const createMyFavoriteMenu = (input: UserFavoriteMenuCreateDto) => {
input.framework = remoteService.replace.framework;
return defAbpHttp.request<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'CreateMyFavoriteMenuAsync',
data: {
input: input,
},
});
};
export const del = (userId: string, menuId: string) => {
return defAbpHttp.request<void>({
service: remoteService.name,
controller: remoteService.controller,
action: 'DeleteAsync',
params: {
userId: userId,
},
data: {
input: {
menuId: menuId,
},
},
});
};
export const delMyFavoriteMenu = (menuId: string) => {
return defAbpHttp.request<void>({
service: remoteService.name,
controller: remoteService.controller,
action: 'DeleteMyFavoriteMenuAsync',
data: {
input: {
menuId: menuId,
},
},
});
};
export const update = (userId: string, input: UserFavoriteMenuUpdateDto) => {
return defAbpHttp.request<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'UpdateAsync',
params: {
userId: userId,
},
data: {
input: input,
},
});
};
export const updateMyFavoriteMenu = (input: UserFavoriteMenuUpdateDto) => {
return defAbpHttp.request<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'UpdateMyFavoriteMenuAsync',
data: {
input: input,
},
});
};
export const getList = (userId: string) => {
return defAbpHttp.listRequest<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'GetListAsync',
params: {
userId: userId,
framework: remoteService.replace.framework,
},
});
};
export const getMyFavoriteMenuList = () => {
return defAbpHttp.listRequest<UserFavoriteMenuDto>({
service: remoteService.name,
controller: remoteService.controller,
action: 'GetMyFavoriteMenuListAsync',
params: {
framework: remoteService.replace.framework,
},
});
};

28
apps/vue/src/api/platform/user-favorites-menu/model/index.ts

@ -0,0 +1,28 @@
import { AuditedEntityDto, EntityDto, IHasConcurrencyStamp } from '/@/api/model/baseModel';
export interface UserFavoriteMenuDto extends AuditedEntityDto, EntityDto<string> {
menuId: string;
userId: string;
aliasName?: string;
color?: string;
framework: string;
name: string;
displayName: string;
path?: string;
icon?: string;
}
interface UserFavoriteMenuCreateOrUpdateDto {
menuId: string;
color?: string;
aliasName?: string;
icon?: string;
}
export interface UserFavoriteMenuCreateDto extends UserFavoriteMenuCreateOrUpdateDto {
framework: string;
}
export interface UserFavoriteMenuUpdateDto extends UserFavoriteMenuCreateOrUpdateDto, IHasConcurrencyStamp {
}

6
apps/vue/src/locales/lang/en/routes/dashboard.ts

@ -15,11 +15,15 @@ export default {
}
},
menus: {
favoriteMenu: '常用',
more: 'More',
addMenu: 'Add new menu',
manager: 'Manage menu',
selectMenu: 'Select menu',
selectColor: 'Select color'
selectColor: 'Select color',
deleteMenu: 'Delete menu',
defineAliasName: 'Alias name',
defineIcon: 'Icon'
}
},
analysis: 'Analysis',

8
apps/vue/src/locales/lang/zh-CN/routes/dashboard.ts

@ -1,5 +1,5 @@
export default {
dashboard: 'Dashboard',
dashboard: '仪表盘',
about: '关于',
workbench: {
title: '工作台',
@ -15,11 +15,15 @@ export default {
}
},
menus: {
favoriteMenu: '常用',
more: '更多',
addMenu: '添加菜单',
manager: '管理菜单',
selectMenu: '选择菜单',
selectColor: '选择颜色'
selectColor: '选择颜色',
deleteMenu: '移除菜单',
defineAliasName: '自定义别名',
defineIcon: '自定义图标'
}
},
analysis: '分析页',

71
apps/vue/src/views/dashboard/workbench/components/MenuCard.vue

@ -1,10 +1,16 @@
<template>
<div>
<Card :title="title" class="menu-card">
<template #extra>
<!-- <template #extra>
<Button type="link" @click="handleManageMenu">{{ t('routes.dashboard.workbench.menus.more') }}</Button>
</template>
<CardGrid v-for="menu in menus" :key="menu.title" class="menu-card-grid">
</template> -->
<CardGrid
v-for="menu in menus"
:key="menu.title"
class="menu-card-grid"
@click="handleNavigationTo(menu)"
@contextmenu="(e) => handleContext(e, menu)"
>
<span class="flex">
<Icon :icon="menu.icon" :color="menu.color" :size="menu.size ?? 30" />
<span class="text-lg ml-4">{{ menu.title }}</span>
@ -22,24 +28,20 @@
</div>
</template>
<script lang="ts" setup>
import { Button, Card } from 'ant-design-vue';
import { Card } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { useI18n } from '/@/hooks/web/useI18n';
import { useGo } from '/@/hooks/web/usePage';
import { useMessage } from '/@/hooks/web/useMessage';
import { useContextMenu } from '/@/hooks/web/useContextMenu';
import { useModal } from '/@/components/Modal';
import { Menu } from './menuProps';
import MenuReference from './MenuReference.vue';
const CardGrid = Card.Grid;
interface Menu {
title: string;
desc?: string;
icon?: string;
color?: string;
size?: number;
}
const emits = defineEmits(['change', 'register']);
const props = defineProps({
const emits = defineEmits(['change', 'delete', 'register']);
defineProps({
title: {
type: String,
required: true,
@ -50,20 +52,49 @@
}
});
const go = useGo();
const { t } = useI18n();
const { createConfirm } = useMessage();
const [createContextMenu] = useContextMenu();
const [registerReference, { openModal: openReferenceModal }] = useModal();
function handleManageMenu() {
openReferenceModal(true, { defaultCheckedKeys: props.menus });
function handleContext(e: MouseEvent, menu: Menu) {
createContextMenu({
event: e,
items: [
{
label: t('routes.dashboard.workbench.menus.deleteMenu'),
icon: 'ant-design:delete-outlined',
handler: () => {
if (!menu.hasDefault) {
createConfirm({
iconType: 'warning',
title: t('AbpUi.AreYouSure'),
content: t('AbpUi.ItemWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
emits('delete', menu);
},
});
}
},
},
],
});
}
function handleAddNew() {
openReferenceModal(true, { radio: true });
openReferenceModal(true, {});
}
function handleNavigationTo(menu: Menu) {
if (menu.path) {
go(menu.path);
}
}
function handleChange(menus) {
console.log(menus);
emits('change', menus);
function handleChange(menu) {
emits('change', menu);
}
</script>

75
apps/vue/src/views/dashboard/workbench/components/MenuReference.vue

@ -6,29 +6,34 @@
:width="500"
@ok="handleSubmit"
>
<Form :model="formModel" ref="formElRef">
<Form.Item :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }" :label="t('routes.dashboard.workbench.menus.selectColor')" name="color">
<VColorPicker v-model:pureColor="formModel.color" format="hex" />
<span>({{ formModel.color }})</span>
</Form.Item>
<Form.Item :label-col="{ span: 3 }" :wrapper-col="{ span: 20 }" :label="t('routes.dashboard.workbench.menus.selectMenu')" name="menus">
<VTree
checkable
checkStrictly
showIcon
<Form :model="formModel" ref="formElRef" :label-col="{ span: 6 }" :wrapper-col="{ span: 17 }">
<Form.Item :label="t('routes.dashboard.workbench.menus.selectMenu')" name="menuId">
<VTreeSelect
allowClear
treeIcon
:tree-data="menuTreeData"
:fieldNames="{
title: 'displayName',
key: 'id',
label: 'displayName',
value: 'id',
}"
v-model:checkedKeys="formModel.menus"
:selectedKeys="defaultCheckedKeys"
@check="handleMenuCheck"
v-model:value="formModel.menuId"
@select="handleMenuChange"
>
<template #icon="{ dataRef }">
<VIcon v-if="dataRef.meta?.icon" :icon="dataRef.meta.icon" />
<template #title="item">
<VIcon v-if="item.meta?.icon" :icon="item.meta.icon" />
<span>{{ item.meta?.title ?? item.displayName }}</span>
</template>
</VTree>
</VTreeSelect>
</Form.Item>
<Form.Item :label="t('routes.dashboard.workbench.menus.selectColor')" name="color">
<VColorPicker v-model:pureColor="formModel.color" format="hex" />
<span>({{ formModel.color }})</span>
</Form.Item>
<Form.Item :label="t('routes.dashboard.workbench.menus.defineAliasName')" name="aliasName">
<VInput v-model:value="formModel.aliasName" autocomplete="off" />
</Form.Item>
<Form.Item :label="t('routes.dashboard.workbench.menus.defineIcon')" name="icon">
<IconPicker v-model:value="formModel.icon" />
</Form.Item>
</Form>
</BasicModal>
@ -37,37 +42,41 @@
<script lang="ts" setup>
import { reactive, ref, unref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { Form, Tree } from 'ant-design-vue';
import { Form, Input, TreeSelect } from 'ant-design-vue';
import { ColorPicker } from "vue3-colorpicker";
import { Icon } from '/@/components/Icon';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { IconPicker } from '/@/components/Icon';
import { getMenuList } from '/@/api/sys/menu';
import { listToTree } from '/@/utils/helper/treeHelper';
const VTree = Tree;
const VTreeSelect = TreeSelect;
const VColorPicker = ColorPicker;
const VIcon = Icon;
const VInput = Input;
const emits = defineEmits(['change', 'change:keys', 'register']);
const emits = defineEmits(['change', 'register']);
const { t } = useI18n();
const formModel = reactive({
menuId: '',
icon: '',
color: '#000000',
menus: [] as string[],
aliasName: '',
});
const formElRef = ref<any>(null);
const menuTreeData = ref<any[]>([]);
const radio = ref(false);
const defaultCheckedKeys = ref<string[]>([]);
const checkedMenus = ref<any[]>([]);
const [registerModal, { closeModal }] = useModalInner((props) => {
init(props);
fetchMyMenus();
});
function init(props) {
formModel.icon = '';
formModel.menuId = '';
formModel.aliasName = '';
formModel.color = '#000000';
formModel.menus = [];
checkedMenus.value = [];
radio.value = props.radio ?? false;
defaultCheckedKeys.value = props.defaultCheckedKeys ?? [];
}
@ -79,22 +88,18 @@
})
}
function handleMenuChange(_, item) {
formModel.icon = item.meta?.icon;
formModel.aliasName = item.meta?.title ?? item.displayName;
}
function handleSubmit() {
const formEl = unref(formElRef);
formEl?.validate().then(() => {
emits('change:keys', formModel.menus);
emits('change', checkedMenus.value);
emits('change', formModel);
closeModal();
});
}
function handleMenuCheck(checkedKeys, e) {
if (checkedKeys.checked.length > 1 && radio.value) {
const menu = checkedKeys.checked.pop();
formModel.menus = [menu!];
}
checkedMenus.value = e.checkedNodes;
}
</script>
<style lang="less">

49
apps/vue/src/views/dashboard/workbench/components/menuProps.ts

@ -0,0 +1,49 @@
import { useI18n } from '/@/hooks/web/useI18n';
export interface Menu {
id: string,
title: string;
desc?: string;
icon?: string;
color?: string;
size?: number;
path?: string;
hasDefault?: boolean;
}
export function useDefaultMenus() {
const { t } = useI18n();
const defaultMenus: Menu[] = [{
id: '0',
title: t('layout.header.home'),
icon: 'ion:home-outline',
color: '#1fdaca',
path: '/',
hasDefault: true,
},{
id: '1',
title: t('routes.dashboard.dashboard'),
icon: 'ion:grid-outline',
color: '#bf0c2c',
path: '/dashboard/workbench',
hasDefault: true,
},{
id: '2',
title: t('routes.basic.accountSetting'),
icon: 'ant-design:setting-outlined',
color: '#3fb27f',
path: '/account/settings',
hasDefault: true,
},{
id: '3',
title: t('routes.basic.accountCenter'),
icon: 'ant-design:profile-outlined',
color: '#4daf1bc9',
path: '/account/center',
hasDefault: true,
}
];
return defaultMenus;
}

93
apps/vue/src/views/dashboard/workbench/index.vue

@ -3,7 +3,13 @@
<template #headerContent> <WorkbenchHeader /> </template>
<div class="lg:flex">
<div class="lg:w-7/10 w-full !mr-4 enter-y">
<MenuCard class="enter-y" title="常用" :menus="usedMenus" />
<MenuCard
class="enter-y"
:title="t('routes.dashboard.workbench.menus.favoriteMenu')"
:menus="myFavoriteMenus"
@change="handleAddMyFavoriteMenu"
@delete="handleDeleteMyFavoriteMenu"
/>
</div>
<div class="lg:w-3/10 w-full enter-y">
<!-- <QuickNav :loading="loading" class="enter-y" /> -->
@ -18,48 +24,61 @@
</PageWrapper>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ref, onMounted } from 'vue';
import { Card } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import { Menu, useDefaultMenus } from './components/menuProps';
import { useI18n } from '/@/hooks/web/useI18n';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { createMyFavoriteMenu, delMyFavoriteMenu, getMyFavoriteMenuList } from '/@/api/platform/user-favorites-menu';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import MenuCard from './components/MenuCard.vue';
const usedMenus = reactive([
{
title: '首页',
icon: 'ion:home-outline',
color: '#1fdaca',
},
{
title: '仪表盘',
icon: 'ion:grid-outline',
color: '#bf0c2c',
},
{
title: '组件',
icon: 'ion:layers-outline',
color: '#e18525',
},
{
title: '系统管理',
icon: 'ion:settings-outline',
color: '#3fb27f',
},
{
title: '权限管理',
icon: 'ion:key-outline',
color: '#4daf1bc9',
},
{
title: '图表',
icon: 'ion:bar-chart-outline',
color: '#00d8ff',
},
]);
const myFavoriteMenus = ref<Menu[]>(useDefaultMenus());
const loading = ref(true);
const { t } = useI18n();
const { createMessage } = useMessage();
const { L } = useLocalization(['AbpUi']);
setTimeout(() => {
loading.value = false;
}, 1500);
onMounted(fetchMyFavoriteMenus);
function fetchMyFavoriteMenus() {
loading.value = true;
getMyFavoriteMenuList().then((res) => {
const defaultFavmenus = useDefaultMenus();
const defineFavmenus = res.items.map((menu) => {
return {
id: menu.id,
title: menu.aliasName ?? menu.displayName,
icon: menu.icon,
color: menu.color,
path: menu.path,
hasDefault: false,
};
});
myFavoriteMenus.value = defaultFavmenus.concat(defineFavmenus);
}).finally(() => {
loading.value = false;
});
}
function handleAddMyFavoriteMenu(menu) {
createMyFavoriteMenu({
framework: '',
menuId: menu.menuId,
icon: menu.icon,
color: menu.color,
aliasName: menu.aliasName,
}).then(() => {
createMessage.success(L('Successful'));
});
}
function handleDeleteMyFavoriteMenu(menu: Menu) {
delMyFavoriteMenu(menu.id).then(() => {
createMessage.success(L('SuccessfullyDeleted'));
});
}
</script>

Loading…
Cancel
Save