basic.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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, usePreferences } 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 { isDark } = usePreferences();
  79. const showDot = computed(() =>
  80. notifications.value.some((item) => !item.isRead),
  81. );
  82. const menus = computed(() => [
  83. {
  84. handler: () => {
  85. router.push({ name: 'Profile' });
  86. },
  87. icon: 'lucide:user',
  88. text: $t('page.auth.profile'),
  89. },
  90. {
  91. handler: () => {
  92. openWindow(VBEN_DOC_URL, {
  93. target: '_blank',
  94. });
  95. },
  96. icon: BookOpenText,
  97. text: $t('ui.widgets.document'),
  98. },
  99. {
  100. handler: () => {
  101. openWindow(VBEN_GITHUB_URL, {
  102. target: '_blank',
  103. });
  104. },
  105. icon: SvgGithubIcon,
  106. text: 'GitHub',
  107. },
  108. {
  109. handler: () => {
  110. openWindow(`${VBEN_GITHUB_URL}/issues`, {
  111. target: '_blank',
  112. });
  113. },
  114. icon: CircleHelp,
  115. text: $t('ui.widgets.qa'),
  116. },
  117. ]);
  118. const avatar = computed(() => {
  119. return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
  120. });
  121. async function handleLogout() {
  122. await authStore.logout(false);
  123. }
  124. function handleNoticeClear() {
  125. notifications.value = [];
  126. }
  127. function markRead(id: number | string) {
  128. const item = notifications.value.find((item) => item.id === id);
  129. if (item) {
  130. item.isRead = true;
  131. }
  132. }
  133. function remove(id: number | string) {
  134. notifications.value = notifications.value.filter((item) => item.id !== id);
  135. }
  136. function handleMakeAll() {
  137. notifications.value.forEach((item) => (item.isRead = true));
  138. }
  139. const viewAll = () => {};
  140. const handleClick = (item: NotificationItem) => {
  141. // 如果通知项有链接,点击时跳转
  142. if (item.link) {
  143. navigateTo(item.link, item.query, item.state);
  144. }
  145. };
  146. function navigateTo(
  147. link: string,
  148. query?: Record<string, any>,
  149. state?: Record<string, any>,
  150. ) {
  151. if (link.startsWith('http://') || link.startsWith('https://')) {
  152. // 外部链接,在新标签页打开
  153. window.open(link, '_blank');
  154. } else {
  155. // 内部路由链接,支持 query 参数和 state
  156. router.push({
  157. path: link,
  158. query: query || {},
  159. state,
  160. });
  161. }
  162. }
  163. watch(
  164. () => ({
  165. enable: preferences.app.watermark,
  166. content: preferences.app.watermarkContent,
  167. isDark: isDark.value,
  168. }),
  169. async ({ enable, content, isDark: isDarkValue }) => {
  170. if (enable) {
  171. const watermarkColor = isDarkValue
  172. ? 'rgba(255, 255, 255, 0.12)'
  173. : 'rgba(0, 0, 0, 0.12)';
  174. await updateWatermark({
  175. advancedStyle: {
  176. colorStops: [
  177. {
  178. color: watermarkColor,
  179. offset: 0,
  180. },
  181. {
  182. color: watermarkColor,
  183. offset: 1,
  184. },
  185. ],
  186. type: 'linear',
  187. },
  188. content:
  189. content ||
  190. `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
  191. });
  192. } else {
  193. destroyWatermark();
  194. }
  195. },
  196. {
  197. immediate: true,
  198. },
  199. );
  200. </script>
  201. <template>
  202. <BasicLayout @clear-preferences-and-logout="handleLogout">
  203. <template #user-dropdown>
  204. <UserDropdown
  205. :avatar
  206. :menus
  207. :text="userStore.userInfo?.realName"
  208. description="ann.vben@gmail.com"
  209. tag-text="Pro"
  210. @logout="handleLogout"
  211. @clear-preferences-and-logout="handleLogout"
  212. />
  213. </template>
  214. <template #notification>
  215. <Notification
  216. :dot="showDot"
  217. :notifications="notifications"
  218. @clear="handleNoticeClear"
  219. @read="(item) => item.id && markRead(item.id)"
  220. @remove="(item) => item.id && remove(item.id)"
  221. @make-all="handleMakeAll"
  222. @on-click="handleClick"
  223. @view-all="viewAll"
  224. />
  225. </template>
  226. <template #extra>
  227. <AuthenticationLoginExpiredModal
  228. v-model:open="accessStore.loginExpired"
  229. :avatar
  230. >
  231. <LoginForm />
  232. </AuthenticationLoginExpiredModal>
  233. </template>
  234. <template #lock-screen>
  235. <LockScreen :avatar @to-login="handleLogout" />
  236. </template>
  237. </BasicLayout>
  238. </template>