basic.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <script lang="ts" setup>
  2. import type { NotificationItem } from '@vben/layouts';
  3. import { computed, onBeforeMount, ref, watch } from 'vue';
  4. import { useRouter } from 'vue-router';
  5. import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
  6. import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
  7. import { useWatermark } from '@vben/hooks';
  8. import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';
  9. import {
  10. BasicLayout,
  11. LockScreen,
  12. Notification,
  13. UserDropdown,
  14. } from '@vben/layouts';
  15. import { preferences } from '@vben/preferences';
  16. import { useAccessStore, useTabbarStore, useUserStore } from '@vben/stores';
  17. import { openWindow } from '@vben/utils';
  18. import { $t } from '#/locales';
  19. import { useAuthStore } from '#/store';
  20. import LoginForm from '#/views/_core/authentication/login.vue';
  21. const { setMenuList } = useTabbarStore();
  22. setMenuList([
  23. 'close',
  24. 'affix',
  25. 'maximize',
  26. 'reload',
  27. 'open-in-new-window',
  28. 'close-left',
  29. 'close-right',
  30. 'close-other',
  31. 'close-all',
  32. ]);
  33. const notifications = ref<NotificationItem[]>([
  34. {
  35. id: 1,
  36. avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
  37. date: '3小时前',
  38. isRead: true,
  39. message: '描述信息描述信息描述信息',
  40. title: '收到了 14 份新周报',
  41. },
  42. {
  43. id: 2,
  44. avatar: 'https://avatar.vercel.sh/1',
  45. date: '刚刚',
  46. isRead: false,
  47. message: '描述信息描述信息描述信息',
  48. title: '朱偏右 回复了你',
  49. },
  50. {
  51. id: 3,
  52. avatar: 'https://avatar.vercel.sh/1',
  53. date: '2024-01-01',
  54. isRead: false,
  55. message: '描述信息描述信息描述信息',
  56. title: '曲丽丽 评论了你',
  57. },
  58. {
  59. id: 4,
  60. avatar: 'https://avatar.vercel.sh/satori',
  61. date: '1天前',
  62. isRead: false,
  63. message: '描述信息描述信息描述信息',
  64. title: '代办提醒',
  65. },
  66. {
  67. id: 5,
  68. avatar: 'https://avatar.vercel.sh/satori',
  69. date: '1天前',
  70. isRead: false,
  71. message: '描述信息描述信息描述信息',
  72. title: '跳转Workspace示例',
  73. link: '/workspace',
  74. },
  75. {
  76. id: 6,
  77. avatar: 'https://avatar.vercel.sh/satori',
  78. date: '1天前',
  79. isRead: false,
  80. message: '描述信息描述信息描述信息',
  81. title: '跳转外部链接示例',
  82. link: 'https://doc.vben.pro',
  83. },
  84. ]);
  85. const router = useRouter();
  86. const userStore = useUserStore();
  87. const authStore = useAuthStore();
  88. const accessStore = useAccessStore();
  89. const { destroyWatermark, updateWatermark } = useWatermark();
  90. const showDot = computed(() =>
  91. notifications.value.some((item) => !item.isRead),
  92. );
  93. const menus = computed(() => [
  94. {
  95. handler: () => {
  96. router.push({ name: 'Profile' });
  97. },
  98. icon: 'lucide:user',
  99. text: $t('page.auth.profile'),
  100. },
  101. {
  102. handler: () => {
  103. openWindow(VBEN_DOC_URL, {
  104. target: '_blank',
  105. });
  106. },
  107. icon: BookOpenText,
  108. text: $t('ui.widgets.document'),
  109. },
  110. {
  111. handler: () => {
  112. openWindow(VBEN_GITHUB_URL, {
  113. target: '_blank',
  114. });
  115. },
  116. icon: SvgGithubIcon,
  117. text: 'GitHub',
  118. },
  119. {
  120. handler: () => {
  121. openWindow(`${VBEN_GITHUB_URL}/issues`, {
  122. target: '_blank',
  123. });
  124. },
  125. icon: CircleHelp,
  126. text: $t('ui.widgets.qa'),
  127. },
  128. ]);
  129. const avatar = computed(() => {
  130. return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
  131. });
  132. async function handleLogout() {
  133. await authStore.logout(false);
  134. }
  135. function handleNoticeClear() {
  136. notifications.value = [];
  137. }
  138. function markRead(id: number | string) {
  139. const item = notifications.value.find((item) => item.id === id);
  140. if (item) {
  141. item.isRead = true;
  142. }
  143. }
  144. function remove(id: number | string) {
  145. notifications.value = notifications.value.filter((item) => item.id !== id);
  146. }
  147. function handleMakeAll() {
  148. notifications.value.forEach((item) => (item.isRead = true));
  149. }
  150. function handleClickLogo() {}
  151. const viewAll = () => {};
  152. const handleClick = (item: NotificationItem) => {
  153. // 如果通知项有链接,点击时跳转
  154. if (item.link) {
  155. navigateTo(item.link, item.query, item.state);
  156. }
  157. };
  158. function navigateTo(
  159. link: string,
  160. query?: Record<string, any>,
  161. state?: Record<string, any>,
  162. ) {
  163. if (link.startsWith('http://') || link.startsWith('https://')) {
  164. // 外部链接,在新标签页打开
  165. window.open(link, '_blank');
  166. } else {
  167. // 内部路由链接,支持 query 参数和 state
  168. router.push({
  169. path: link,
  170. query: query || {},
  171. state,
  172. });
  173. }
  174. }
  175. watch(
  176. () => ({
  177. enable: preferences.app.watermark,
  178. content: preferences.app.watermarkContent,
  179. }),
  180. async ({ enable, content }) => {
  181. if (enable) {
  182. await updateWatermark({
  183. content:
  184. content ||
  185. `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
  186. });
  187. } else {
  188. destroyWatermark();
  189. }
  190. },
  191. {
  192. immediate: true,
  193. },
  194. );
  195. onBeforeMount(() => {
  196. if (preferences.app.watermark) {
  197. destroyWatermark();
  198. }
  199. });
  200. </script>
  201. <template>
  202. <BasicLayout
  203. @clear-preferences-and-logout="handleLogout"
  204. @click-logo="handleClickLogo"
  205. >
  206. <template #user-dropdown>
  207. <UserDropdown
  208. :avatar
  209. :menus
  210. :text="userStore.userInfo?.realName"
  211. description="ann.vben@gmail.com"
  212. tag-text="Pro"
  213. trigger="both"
  214. @logout="handleLogout"
  215. />
  216. </template>
  217. <template #notification>
  218. <Notification
  219. :dot="showDot"
  220. :notifications="notifications"
  221. @clear="handleNoticeClear"
  222. @read="(item) => item.id && markRead(item.id)"
  223. @remove="(item) => item.id && remove(item.id)"
  224. @make-all="handleMakeAll"
  225. @on-click="handleClick"
  226. @view-all="viewAll"
  227. />
  228. </template>
  229. <template #extra>
  230. <AuthenticationLoginExpiredModal
  231. v-model:open="accessStore.loginExpired"
  232. :avatar
  233. >
  234. <LoginForm />
  235. </AuthenticationLoginExpiredModal>
  236. </template>
  237. <template #lock-screen>
  238. <LockScreen :avatar @to-login="handleLogout" />
  239. </template>
  240. </BasicLayout>
  241. </template>