basic.vue 5.6 KB

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