committed by
GitHub
10 changed files with 483 additions and 248 deletions
@ -1,266 +1,32 @@ |
|||
<script lang="ts" setup> |
|||
import type { |
|||
WorkbenchProjectItem, |
|||
WorkbenchQuickNavItem, |
|||
WorkbenchTodoItem, |
|||
WorkbenchTrendItem, |
|||
} from '@vben/common-ui'; |
|||
<script setup lang="ts"> |
|||
import type { FavoriteMenu } from '@abp/platform'; |
|||
|
|||
import { ref } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
import { |
|||
AnalysisChartCard, |
|||
WorkbenchHeader, |
|||
WorkbenchProject, |
|||
WorkbenchQuickNav, |
|||
WorkbenchTodo, |
|||
WorkbenchTrends, |
|||
} from '@vben/common-ui'; |
|||
import { preferences } from '@vben/preferences'; |
|||
import { useUserStore } from '@vben/stores'; |
|||
import { openWindow } from '@vben/utils'; |
|||
|
|||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; |
|||
|
|||
const userStore = useUserStore(); |
|||
|
|||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整 |
|||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转 |
|||
// 例如:url: /dashboard/workspace |
|||
const projectItems: WorkbenchProjectItem[] = [ |
|||
{ |
|||
color: '', |
|||
content: '不要等待机会,而要创造机会。', |
|||
date: '2021-04-01', |
|||
group: '开源组', |
|||
icon: 'carbon:logo-github', |
|||
title: 'Github', |
|||
url: 'https://github.com', |
|||
}, |
|||
{ |
|||
color: '#3fb27f', |
|||
content: '现在的你决定将来的你。', |
|||
date: '2021-04-01', |
|||
group: '算法组', |
|||
icon: 'ion:logo-vue', |
|||
title: 'Vue', |
|||
url: 'https://vuejs.org', |
|||
}, |
|||
{ |
|||
color: '#e18525', |
|||
content: '没有什么才能比努力更重要。', |
|||
date: '2021-04-01', |
|||
group: '上班摸鱼', |
|||
icon: 'ion:logo-html5', |
|||
title: 'Html5', |
|||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML', |
|||
}, |
|||
{ |
|||
color: '#bf0c2c', |
|||
content: '热情和欲望可以突破一切难关。', |
|||
date: '2021-04-01', |
|||
group: 'UI', |
|||
icon: 'ion:logo-angular', |
|||
title: 'Angular', |
|||
url: 'https://angular.io', |
|||
}, |
|||
{ |
|||
color: '#00d8ff', |
|||
content: '健康的身体是实现目标的基石。', |
|||
date: '2021-04-01', |
|||
group: '技术牛', |
|||
icon: 'bx:bxl-react', |
|||
title: 'React', |
|||
url: 'https://reactjs.org', |
|||
}, |
|||
{ |
|||
color: '#EBD94E', |
|||
content: '路是走出来的,而不是空想出来的。', |
|||
date: '2021-04-01', |
|||
group: '架构组', |
|||
icon: 'ion:logo-javascript', |
|||
title: 'Js', |
|||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript', |
|||
}, |
|||
]; |
|||
|
|||
// 同样,这里的 url 也可以使用以 http 开头的外部链接 |
|||
const quickNavItems: WorkbenchQuickNavItem[] = [ |
|||
{ |
|||
color: '#1fdaca', |
|||
icon: 'ion:home-outline', |
|||
title: '首页', |
|||
url: '/', |
|||
}, |
|||
{ |
|||
color: '#bf0c2c', |
|||
icon: 'ion:grid-outline', |
|||
title: '仪表盘', |
|||
url: '/dashboard', |
|||
}, |
|||
{ |
|||
color: '#e18525', |
|||
icon: 'ion:layers-outline', |
|||
title: '组件', |
|||
url: '/demos/features/icons', |
|||
}, |
|||
{ |
|||
color: '#3fb27f', |
|||
icon: 'ion:settings-outline', |
|||
title: '系统管理', |
|||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整 |
|||
}, |
|||
{ |
|||
color: '#4daf1bc9', |
|||
icon: 'ion:key-outline', |
|||
title: '权限管理', |
|||
url: '/demos/access/page-control', |
|||
}, |
|||
{ |
|||
color: '#00d8ff', |
|||
icon: 'ion:bar-chart-outline', |
|||
title: '图表', |
|||
url: '/analytics', |
|||
}, |
|||
]; |
|||
|
|||
const todoItems = ref<WorkbenchTodoItem[]>([ |
|||
{ |
|||
completed: false, |
|||
content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '审查前端代码提交', |
|||
}, |
|||
{ |
|||
completed: true, |
|||
content: `检查并优化系统性能,降低CPU使用率。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '系统性能优化', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '安全检查', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `更新项目中的所有npm依赖包,确保使用最新版本。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '更新项目依赖', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '修复UI显示问题', |
|||
}, |
|||
]); |
|||
const trendItems: WorkbenchTrendItem[] = [ |
|||
{ |
|||
avatar: 'svg:avatar-1', |
|||
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`, |
|||
date: '刚刚', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-2', |
|||
content: `关注了 <a>威廉</a> `, |
|||
date: '1个小时前', |
|||
title: '艾文', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-3', |
|||
content: `发布了 <a>个人动态</a> `, |
|||
date: '1天前', |
|||
title: '克里斯', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `发表文章 <a>如何编写一个Vite插件</a> `, |
|||
date: '2天前', |
|||
title: 'Vben', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-1', |
|||
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`, |
|||
date: '3天前', |
|||
title: '皮特', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-2', |
|||
content: `关闭了问题 <a>如何运行项目</a> `, |
|||
date: '1周前', |
|||
title: '杰克', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-3', |
|||
content: `发布了 <a>个人动态</a> `, |
|||
date: '1周前', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `推送了代码到 <a>Github</a>`, |
|||
date: '2021-04-01 20:00', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `发表文章 <a>如何编写使用 Admin Vben</a> `, |
|||
date: '2021-03-01 20:00', |
|||
title: 'Vben', |
|||
}, |
|||
]; |
|||
import { Workbench } from '@abp/platform'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整 |
|||
// This is a sample method, adjust according to the actual project requirements |
|||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) { |
|||
if (nav.url?.startsWith('http')) { |
|||
openWindow(nav.url); |
|||
function navTo(menu: FavoriteMenu) { |
|||
if (menu.path?.startsWith('http')) { |
|||
openWindow(menu.path); |
|||
return; |
|||
} |
|||
if (nav.url?.startsWith('/')) { |
|||
router.push(nav.url).catch((error) => { |
|||
if (menu.path?.startsWith('/')) { |
|||
router.push(menu.path).catch((error) => { |
|||
console.error('Navigation failed:', error); |
|||
}); |
|||
} else { |
|||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`); |
|||
console.warn( |
|||
`Unknown URL for navigation item: ${menu.displayName} -> ${menu.path}`, |
|||
); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="p-5"> |
|||
<WorkbenchHeader |
|||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar" |
|||
> |
|||
<template #title> |
|||
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧! |
|||
</template> |
|||
<template #description> 今日晴,20℃ - 32℃! </template> |
|||
</WorkbenchHeader> |
|||
|
|||
<div class="mt-5 flex flex-col lg:flex-row"> |
|||
<div class="mr-4 w-full lg:w-3/5"> |
|||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" /> |
|||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" /> |
|||
</div> |
|||
<div class="w-full lg:w-2/5"> |
|||
<WorkbenchQuickNav |
|||
:items="quickNavItems" |
|||
class="mt-5 lg:mt-0" |
|||
title="快捷导航" |
|||
@click="navTo" |
|||
/> |
|||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> |
|||
<AnalysisChartCard class="mt-5" title="访问来源"> |
|||
<AnalyticsVisitsSource /> |
|||
</AnalysisChartCard> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<Workbench @nav-to="navTo" /> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
|
|||
@ -0,0 +1,61 @@ |
|||
<script lang="ts" setup> |
|||
import { onMounted, ref } from 'vue'; |
|||
|
|||
import { VbenAvatar } from '@vben-core/shadcn-ui'; |
|||
|
|||
import { NotificationReadState, useMyNotifilersApi } from '@abp/notifications'; |
|||
|
|||
interface Props { |
|||
avatar?: string; |
|||
notifierCount?: number; |
|||
text?: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchHeader', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
avatar: '', |
|||
text: '', |
|||
notifierCount: 0, |
|||
}); |
|||
const unReadNotifilerCount = ref(0); |
|||
const { getMyNotifilersApi } = useMyNotifilersApi(); |
|||
|
|||
async function onInit() { |
|||
const { totalCount } = await getMyNotifilersApi({ |
|||
maxResultCount: 1, |
|||
readState: NotificationReadState.UnRead, |
|||
}); |
|||
unReadNotifilerCount.value = totalCount; |
|||
} |
|||
|
|||
onMounted(onInit); |
|||
</script> |
|||
<template> |
|||
<div class="card-box p-4 py-6 lg:flex"> |
|||
<VbenAvatar :alt="text" :src="avatar" class="size-20" /> |
|||
<div |
|||
v-if="$slots.title || $slots.description" |
|||
class="flex flex-col justify-center md:ml-6 md:mt-0" |
|||
> |
|||
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl"> |
|||
<slot name="title"></slot> |
|||
</h1> |
|||
<span v-if="$slots.description" class="text-foreground/80 mt-1"> |
|||
<slot name="description"></slot> |
|||
</span> |
|||
</div> |
|||
<div class="mt-4 flex flex-1 justify-end md:mt-0"> |
|||
<div class="flex flex-col justify-center text-right"> |
|||
<span class="text-foreground/80"> |
|||
{{ $t('workbench.header.notifier.title') }} |
|||
</span> |
|||
<a class="text-2xl">{{ |
|||
$t('workbench.header.notifier.count', [notifierCount]) |
|||
}}</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,60 @@ |
|||
<script setup lang="ts"> |
|||
import type { FavoriteMenu } from '../types'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenIcon, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
items?: FavoriteMenu[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchQuickNav', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
|
|||
defineEmits<{ |
|||
(event: 'click', menu: FavoriteMenu): void; |
|||
}>(); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<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> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,64 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTodoItem } from '@vben/common-ui'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenCheckbox, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
items?: WorkbenchTodoItem[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchTodo', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<CardTitle class="text-lg">{{ title }}</CardTitle> |
|||
</CardHeader> |
|||
<slot v-if="items.length === 0" name="empty"></slot> |
|||
<CardContent v-else class="flex flex-wrap p-5 pt-0"> |
|||
<ul class="divide-border w-full divide-y" role="list"> |
|||
<li |
|||
v-for="item in items" |
|||
:key="item.title" |
|||
:class="{ |
|||
'select-none line-through opacity-60': item.completed, |
|||
}" |
|||
class="flex cursor-pointer justify-between gap-x-6 py-5" |
|||
> |
|||
<div class="flex min-w-0 items-center gap-x-4"> |
|||
<VbenCheckbox v-model:checked="item.completed" name="completed" /> |
|||
<div class="min-w-0 flex-auto"> |
|||
<p class="text-foreground text-sm font-semibold leading-6"> |
|||
{{ item.title }} |
|||
</p> |
|||
<!-- eslint-disable vue/no-v-html --> |
|||
<p |
|||
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5" |
|||
v-html="item.content" |
|||
></p> |
|||
</div> |
|||
</div> |
|||
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end"> |
|||
<span class="text-foreground/80 mt-6 text-xs leading-6"> |
|||
{{ item.date }} |
|||
</span> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
@ -0,0 +1,65 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTrendItem } from '@vben/common-ui'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenIcon, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
items?: WorkbenchTrendItem[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchTrends', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<CardTitle class="text-lg">{{ title }}</CardTitle> |
|||
</CardHeader> |
|||
<slot v-if="items.length === 0" name="empty"></slot> |
|||
<CardContent v-else class="flex flex-wrap p-5 pt-0"> |
|||
<ul class="divide-border w-full divide-y" role="list"> |
|||
<li |
|||
v-for="item in items" |
|||
:key="item.title" |
|||
class="flex justify-between gap-x-6 py-5" |
|||
> |
|||
<div class="flex min-w-0 items-center gap-x-4"> |
|||
<VbenIcon |
|||
:icon="item.avatar" |
|||
alt="" |
|||
class="size-10 flex-none rounded-full" |
|||
/> |
|||
<div class="min-w-0 flex-auto"> |
|||
<p class="text-foreground text-sm font-semibold leading-6"> |
|||
{{ item.title }} |
|||
</p> |
|||
<!-- eslint-disable vue/no-v-html --> |
|||
<p |
|||
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5" |
|||
v-html="item.content" |
|||
></p> |
|||
</div> |
|||
</div> |
|||
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end"> |
|||
<span class="text-foreground/80 mt-6 text-xs leading-6"> |
|||
{{ item.date }} |
|||
</span> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
@ -0,0 +1,195 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTodoItem, WorkbenchTrendItem } from '@vben/common-ui'; |
|||
|
|||
import type { FavoriteMenu } from './types'; |
|||
|
|||
import { computed, onMounted, ref } from 'vue'; |
|||
|
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { $t } from '@vben/locales'; |
|||
import { preferences } from '@vben/preferences'; |
|||
import { useUserStore } from '@vben/stores'; |
|||
|
|||
import { formatToDateTime } from '@abp/core'; |
|||
import { |
|||
NotificationReadState, |
|||
useMyNotifilersApi, |
|||
useNotificationSerializer, |
|||
} from '@abp/notifications'; |
|||
import { Empty } from 'ant-design-vue'; |
|||
|
|||
import { useMyFavoriteMenusApi } from '../../api/useMyFavoriteMenusApi'; |
|||
import WorkbenchHeader from './components/WorkbenchHeader.vue'; |
|||
import WorkbenchQuickNav from './components/WorkbenchQuickNav.vue'; |
|||
import WorkbenchTodo from './components/WorkbenchTodo.vue'; |
|||
import WorkbenchTrends from './components/WorkbenchTrends.vue'; |
|||
|
|||
defineEmits<{ |
|||
(event: 'navTo', menu: FavoriteMenu): void; |
|||
}>(); |
|||
|
|||
const userStore = useUserStore(); |
|||
const { getMyNotifilersApi } = useMyNotifilersApi(); |
|||
const { getListApi: getFavoriteMenusApi } = useMyFavoriteMenusApi(); |
|||
const { deserialize } = useNotificationSerializer(); |
|||
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
|
|||
const defaultMenus: FavoriteMenu[] = [ |
|||
{ |
|||
id: '1', |
|||
color: '#1fdaca', |
|||
icon: 'ion:home-outline', |
|||
displayName: $t('workbench.content.favoriteMenu.home'), |
|||
path: '/', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '2', |
|||
color: '#bf0c2c', |
|||
icon: 'ion:grid-outline', |
|||
displayName: $t('workbench.content.favoriteMenu.dashboard'), |
|||
path: '/', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '3', |
|||
color: '#00d8ff', |
|||
icon: 'ant-design:notification-outlined', |
|||
displayName: $t('workbench.content.favoriteMenu.notifiers'), |
|||
path: '/manage/notifications/my-notifilers', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '4', |
|||
color: '#4daf1bc9', |
|||
icon: 'tdesign:user-setting', |
|||
displayName: $t('workbench.content.favoriteMenu.settings'), |
|||
path: '/account/my-settings', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '5', |
|||
color: '#3fb27f', |
|||
icon: 'hugeicons:profile-02', |
|||
displayName: $t('workbench.content.favoriteMenu.profile'), |
|||
path: '/account/profile', |
|||
isDefault: true, |
|||
}, |
|||
]; |
|||
const unReadNotifilerCount = ref(0); |
|||
const unReadNotifilers = ref<WorkbenchTrendItem[]>([]); |
|||
const favoriteMenus = ref<FavoriteMenu[]>([]); |
|||
const todoList = ref<WorkbenchTodoItem[]>([]); |
|||
|
|||
const getFavoriteMenus = computed(() => { |
|||
return [...defaultMenus, ...favoriteMenus.value]; |
|||
}); |
|||
const getWelcomeTitle = computed(() => { |
|||
const now = new Date(); |
|||
const hour = now.getHours(); |
|||
if (hour < 12) { |
|||
return $t('workbench.header.welcome.morning', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
if (hour < 14) { |
|||
return $t('workbench.header.welcome.atoon', [userStore.userInfo?.realName]); |
|||
} |
|||
if (hour < 17) { |
|||
return $t('workbench.header.welcome.afternoon', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
if (hour < 24) { |
|||
return $t('workbench.header.welcome.evening', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
return ''; |
|||
}); |
|||
async function onInit() { |
|||
await Promise.all([ |
|||
onInitFavoriteMenus(), |
|||
onInitNotifiers(), |
|||
onInitTodoList(), |
|||
]); |
|||
} |
|||
async function onInitFavoriteMenus() { |
|||
const { items } = await getFavoriteMenusApi(uiFramework); |
|||
favoriteMenus.value = items.map((item) => { |
|||
return { |
|||
...item, |
|||
isDefault: false, |
|||
}; |
|||
}); |
|||
} |
|||
async function onInitNotifiers() { |
|||
const { items, totalCount } = await getMyNotifilersApi({ |
|||
maxResultCount: 10, |
|||
readState: NotificationReadState.UnRead, |
|||
}); |
|||
unReadNotifilers.value = items.map((item) => { |
|||
const notifier = deserialize(item); |
|||
return { |
|||
avatar: '', |
|||
date: formatToDateTime(item.creationTime), |
|||
title: notifier.title, |
|||
content: notifier.message, |
|||
}; |
|||
}); |
|||
unReadNotifilerCount.value = totalCount; |
|||
} |
|||
async function onInitTodoList() { |
|||
// TODO: 实现待办事项列表 |
|||
todoList.value = []; |
|||
} |
|||
|
|||
onMounted(onInit); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="p-5"> |
|||
<WorkbenchHeader |
|||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar" |
|||
:text="userStore.userInfo?.realName" |
|||
:notifier-count="unReadNotifilerCount" |
|||
> |
|||
<template #title> |
|||
{{ getWelcomeTitle }} |
|||
</template> |
|||
<template #description> 今日晴,20℃ - 32℃! </template> |
|||
</WorkbenchHeader> |
|||
|
|||
<div class="mt-5 flex flex-col lg:flex-row"> |
|||
<div class="mr-4 w-full lg:w-3/5"> |
|||
<WorkbenchQuickNav |
|||
:items="getFavoriteMenus" |
|||
class="mt-5 lg:mt-0" |
|||
:title="$t('workbench.content.favoriteMenu.title')" |
|||
@click="(menu: FavoriteMenu) => $emit('navTo', menu)" |
|||
/> |
|||
<WorkbenchTodo |
|||
:items="todoList" |
|||
class="mt-5" |
|||
:title="$t('workbench.content.todo.title')" |
|||
> |
|||
<template #empty> |
|||
<Empty /> |
|||
</template> |
|||
</WorkbenchTodo> |
|||
</div> |
|||
<div class="w-full lg:w-2/5"> |
|||
<WorkbenchTrends |
|||
:items="unReadNotifilers" |
|||
:title="$t('workbench.content.trends.title')" |
|||
> |
|||
<template #empty> |
|||
<Empty /> |
|||
</template> |
|||
</WorkbenchTrends> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,10 @@ |
|||
interface FavoriteMenu { |
|||
color?: string; |
|||
displayName: string; |
|||
icon?: string; |
|||
id: string; |
|||
isDefault: boolean; |
|||
path?: string; |
|||
} |
|||
|
|||
export type { FavoriteMenu }; |
|||
Loading…
Reference in new issue