header.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. <template>
  2. <header
  3. class="fixed w-full top-0 z-100 bg-white border-b border-[#f0f2f5] shadow-sm"
  4. >
  5. <div
  6. class="max-w-[1200px] mx-auto flex items-center justify-between px-10 pr-0 h-20"
  7. >
  8. <div class="flex items-center gap-2 cursor-pointer" @click="goHome">
  9. <img :src="logo" alt="logo" class="w-9 h-9 rounded-md" />
  10. <span class="text-[#02409b] text-[20px] font-bold">KERUI DEEPOIL</span>
  11. <span class="text-[#828182] text-[14px]">智慧经营平台</span>
  12. </div>
  13. <nav class="hidden lg:flex flex-1 mx-4 ml-10 text-sm">
  14. <ul class="flex items-center gap-6 text-[#303133] text-lg">
  15. <li>
  16. <a
  17. class="hover:text-[#02409b] cursor-pointer"
  18. @click="router.push('/')"
  19. >首页</a
  20. >
  21. </li>
  22. <li>
  23. <a class="hover:text-[#02409b] cursor-pointer" @click="goFlow"
  24. >流程门户</a
  25. >
  26. </li>
  27. <li>
  28. <a class="hover:text-[#02409b] cursor-pointer" @click="goDrive"
  29. >驾驶舱门户</a
  30. >
  31. </li>
  32. <li><a class="hover:text-[#02409b] cursor-pointer">报表门户</a></li>
  33. </ul>
  34. </nav>
  35. <div class="hidden lg:flex items-center gap-3 h-full">
  36. <!-- 消息中心 -->
  37. <el-dropdown trigger="click" placement="bottom-end">
  38. <div class="flex items-center gap-2 cursor-pointer pr-6 pt-2">
  39. <el-badge
  40. :value="unreadMessageCount + oaUnreadCount"
  41. class="item"
  42. v-if="hasUnreadMessages || oaHasUnreadCount"
  43. >
  44. <Icon
  45. icon="mdi:bell"
  46. class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
  47. />
  48. </el-badge>
  49. <Icon
  50. v-else
  51. icon="mdi:bell"
  52. class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
  53. />
  54. </div>
  55. <template #dropdown>
  56. <el-dropdown-menu class="notification-dropdown">
  57. <div class="notification-tabs pl-2">
  58. <el-tabs v-model="activeTab" class="demo-tabs">
  59. <el-tab-pane label="OA" name="tasks">
  60. <template #label>
  61. <span class="custom-tabs-label">
  62. <span>OA</span>
  63. <el-badge
  64. :value="oaUnreadCount"
  65. class="item ml-1"
  66. v-if="oaHasUnreadCount"
  67. ></el-badge>
  68. </span>
  69. </template>
  70. <div class="tab-content">
  71. <div>
  72. <span
  73. v-if="oaHasUnreadCount"
  74. class="cursor-pointer text-blue-500"
  75. @click="oaMarkAllAsRead"
  76. >全部标为已读</span
  77. >
  78. </div>
  79. <!-- OA消息 -->
  80. <div
  81. class="task-item"
  82. v-for="(task, index) in oaMessagesList"
  83. :key="index"
  84. >
  85. <div class="task-info">
  86. <p class="task-title">
  87. <span
  88. v-if="task.status === '0'"
  89. class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
  90. ></span>
  91. {{ task.title }}
  92. </p>
  93. <p class="message-desc">
  94. <span>{{ task.oaCreateTime }}</span>
  95. </p>
  96. </div>
  97. </div>
  98. <div v-if="!oaMessagesList.length" class="no-tasks">
  99. 暂无新消息
  100. </div>
  101. </div>
  102. </el-tab-pane>
  103. <el-tab-pane label="CRM" name="messages">
  104. <template #label>
  105. <span class="custom-tabs-label">
  106. <span>CRM</span>
  107. <el-badge
  108. :value="unreadMessageCount"
  109. class="item ml-1"
  110. v-if="hasUnreadMessages"
  111. ></el-badge>
  112. </span>
  113. </template>
  114. <div class="tab-content">
  115. <!-- 消息中心内容 -->
  116. <div>
  117. <span
  118. v-if="hasUnreadMessages"
  119. class="cursor-pointer text-blue-500"
  120. @click="markAllAsRead"
  121. >全部标为已读</span
  122. >
  123. </div>
  124. <div
  125. class="message-item"
  126. v-for="(item, index) in messages"
  127. :key="index"
  128. >
  129. <div class="message-icon"></div>
  130. <div class="message-text">
  131. <!-- 未读就显示小红点 -->
  132. <p class="message-title flex items-center gap-5">
  133. <span
  134. v-if="item.status === '0'"
  135. class="w-2 h-2 bg-[#f56c6c] rounded-full"
  136. ></span
  137. >{{ item.contentMajor }}
  138. </p>
  139. <p class="message-desc">
  140. {{ timestampToDateTime(item.createTime) }}
  141. </p>
  142. </div>
  143. </div>
  144. <div v-if="!messages.length" class="no-messages">
  145. 暂无新消息
  146. </div>
  147. </div>
  148. </el-tab-pane>
  149. </el-tabs>
  150. </div>
  151. </el-dropdown-menu>
  152. </template>
  153. </el-dropdown>
  154. <template v-if="isLoggedIn">
  155. <el-dropdown @command="onUserCommand" trigger="click">
  156. <span class="flex items-center gap-2 cursor-pointer pr-2">
  157. <div class="avatar-wrapper">
  158. <img
  159. :src="userAvatar || person"
  160. alt="avatar"
  161. class="w-8 h-8 rounded-full avatar-image"
  162. />
  163. </div>
  164. <span class="text-sm text-[#303133]">{{ userName }}</span>
  165. </span>
  166. <template #dropdown>
  167. <el-dropdown-menu>
  168. <el-dropdown-item command="profile">
  169. <svg
  170. xmlns="http://www.w3.org/2000/svg"
  171. width="18"
  172. height="18"
  173. viewBox="0 0 16 16"
  174. >
  175. <g fill="none">
  176. <path
  177. fill="url(#SVG3BqCJdyi)"
  178. d="M11.5 8A1.5 1.5 0 0 1 13 9.5v.5c0 1.971-1.86 4-5 4s-5-2.029-5-4v-.5A1.5 1.5 0 0 1 4.5 8z"
  179. />
  180. <path
  181. fill="url(#SVGfKhxtenh)"
  182. d="M11.5 8A1.5 1.5 0 0 1 13 9.5v.5c0 1.971-1.86 4-5 4s-5-2.029-5-4v-.5A1.5 1.5 0 0 1 4.5 8z"
  183. />
  184. <path
  185. fill="url(#SVGJYCMTblH)"
  186. d="M8 1.5A2.75 2.75 0 1 1 8 7a2.75 2.75 0 0 1 0-5.5"
  187. />
  188. <defs>
  189. <linearGradient
  190. id="SVG3BqCJdyi"
  191. x1="5.378"
  192. x2="7.616"
  193. y1="8.798"
  194. y2="14.754"
  195. gradientUnits="userSpaceOnUse"
  196. >
  197. <stop offset=".125" stop-color="#9c6cfe" />
  198. <stop offset="1" stop-color="#7a41dc" />
  199. </linearGradient>
  200. <linearGradient
  201. id="SVGfKhxtenh"
  202. x1="8"
  203. x2="11.164"
  204. y1="7.286"
  205. y2="17.139"
  206. gradientUnits="userSpaceOnUse"
  207. >
  208. <stop stop-color="#885edb" stop-opacity="0" />
  209. <stop offset="1" stop-color="#e362f8" />
  210. </linearGradient>
  211. <linearGradient
  212. id="SVGJYCMTblH"
  213. x1="6.558"
  214. x2="9.361"
  215. y1="2.231"
  216. y2="6.707"
  217. gradientUnits="userSpaceOnUse"
  218. >
  219. <stop offset=".125" stop-color="#9c6cfe" />
  220. <stop offset="1" stop-color="#7a41dc" />
  221. </linearGradient>
  222. </defs>
  223. </g>
  224. </svg>
  225. <span class="pl-2">个人中心</span>
  226. </el-dropdown-item>
  227. <el-dropdown-item command="logout">
  228. <svg
  229. xmlns="http://www.w3.org/2000/svg"
  230. width="18"
  231. height="18"
  232. viewBox="0 0 24 24"
  233. >
  234. <g fill="none">
  235. <path
  236. fill="url(#SVG0pAmxd9w)"
  237. d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2"
  238. />
  239. <path
  240. fill="url(#SVGFnXqmeDt)"
  241. d="m15.53 8.47l-.084-.073a.75.75 0 0 0-.882-.007l-.094.08L12 10.939l-2.47-2.47l-.084-.072a.75.75 0 0 0-.882-.007l-.094.08l-.073.084a.75.75 0 0 0-.007.882l.08.094L10.939 12l-2.47 2.47l-.072.084a.75.75 0 0 0-.007.882l.08.094l.084.073a.75.75 0 0 0 .882.007l.094-.08L12 13.061l2.47 2.47l.084.072a.75.75 0 0 0 .882.007l.094-.08l.073-.084a.75.75 0 0 0 .007-.882l-.08-.094L13.061 12l2.47-2.47l.072-.084a.75.75 0 0 0 .007-.882z"
  242. />
  243. <defs>
  244. <linearGradient
  245. id="SVG0pAmxd9w"
  246. x1="5.125"
  247. x2="18.25"
  248. y1="3.25"
  249. y2="22.625"
  250. gradientUnits="userSpaceOnUse"
  251. >
  252. <stop stop-color="#f83f54" />
  253. <stop offset="1" stop-color="#ca2134" />
  254. </linearGradient>
  255. <linearGradient
  256. id="SVGFnXqmeDt"
  257. x1="8.685"
  258. x2="12.591"
  259. y1="12.332"
  260. y2="16.392"
  261. gradientUnits="userSpaceOnUse"
  262. >
  263. <stop stop-color="#fdfdfd" />
  264. <stop offset="1" stop-color="#fecbe6" />
  265. </linearGradient>
  266. </defs>
  267. </g>
  268. </svg>
  269. <span class="pl-2">退出登录</span>
  270. </el-dropdown-item>
  271. </el-dropdown-menu>
  272. </template>
  273. </el-dropdown>
  274. </template>
  275. <template v-else>
  276. <div
  277. class="bg-[#0050b3] hover:bg-[#0050b3]/90 text-white text-sm flex items-center justify-center cursor-pointer h-full px-10 py-4"
  278. @click="login"
  279. >
  280. 登 录
  281. </div>
  282. </template>
  283. </div>
  284. <div class="lg:hidden">
  285. <el-dropdown trigger="click" placement="bottom-end">
  286. <div class="flex items-center gap-2 cursor-pointer pr-6 pt-1">
  287. <el-badge
  288. :value="unreadMessageCount + oaUnreadCount"
  289. class="item"
  290. v-if="hasUnreadMessages || oaHasUnreadCount"
  291. >
  292. <Icon
  293. icon="mdi:bell"
  294. class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
  295. />
  296. </el-badge>
  297. <Icon
  298. v-else
  299. icon="mdi:bell"
  300. class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
  301. />
  302. </div>
  303. <template #dropdown>
  304. <el-dropdown-menu class="notification-dropdown">
  305. <div class="notification-tabs pl-2">
  306. <el-tabs v-model="activeTab" class="demo-tabs">
  307. <el-tab-pane label="OA" name="tasks">
  308. <template #label>
  309. <span class="custom-tabs-label">
  310. <span>OA</span>
  311. <el-badge
  312. :value="oaUnreadCount"
  313. class="item ml-1"
  314. v-if="oaHasUnreadCount"
  315. ></el-badge>
  316. </span>
  317. </template>
  318. <div class="tab-content">
  319. <div>
  320. <span
  321. v-if="oaHasUnreadCount"
  322. class="cursor-pointer text-blue-500"
  323. @click="oaMarkAllAsRead"
  324. >全部标为已读</span
  325. >
  326. </div>
  327. <!-- OA消息 -->
  328. <div
  329. class="task-item"
  330. v-for="(task, index) in oaMessagesList"
  331. :key="index"
  332. >
  333. <div class="task-info">
  334. <p class="task-title">
  335. <span
  336. v-if="task.status === '0'"
  337. class="inline-block h-2 w-2 bg-[#f56c6c] rounded-full"
  338. ></span>
  339. {{ task.title }}
  340. </p>
  341. <p class="message-desc">
  342. <span>{{ task.oaCreateTime }}</span>
  343. </p>
  344. </div>
  345. </div>
  346. <div v-if="!oaMessagesList.length" class="no-tasks">
  347. 暂无新消息
  348. </div>
  349. </div>
  350. </el-tab-pane>
  351. <el-tab-pane label="CRM" name="messages">
  352. <template #label>
  353. <span class="custom-tabs-label">
  354. <span>CRM</span>
  355. <el-badge
  356. :value="unreadMessageCount"
  357. class="item ml-1"
  358. v-if="hasUnreadMessages"
  359. ></el-badge>
  360. </span>
  361. </template>
  362. <div class="tab-content">
  363. <!-- 消息中心内容 -->
  364. <div>
  365. <span
  366. v-if="hasUnreadMessages"
  367. class="cursor-pointer text-blue-500"
  368. @click="markAllAsRead"
  369. >全部标为已读</span
  370. >
  371. </div>
  372. <div
  373. class="message-item"
  374. v-for="(item, index) in messages"
  375. :key="index"
  376. >
  377. <div class="message-icon"></div>
  378. <div class="message-text">
  379. <!-- 未读就显示小红点 -->
  380. <p class="message-title flex items-center gap-5">
  381. <span
  382. v-if="item.status === '0'"
  383. class="w-2 h-2 bg-[#f56c6c] rounded-full"
  384. ></span
  385. >{{ item.contentMajor }}
  386. </p>
  387. <p class="message-desc">
  388. {{ timestampToDateTime(item.createTime) }}
  389. </p>
  390. </div>
  391. </div>
  392. <div v-if="!messages.length" class="no-messages">
  393. 暂无新消息
  394. </div>
  395. </div>
  396. </el-tab-pane>
  397. </el-tabs>
  398. </div>
  399. </el-dropdown-menu>
  400. </template>
  401. </el-dropdown>
  402. <el-button link @click="drawer = true">
  403. <i class="el-icon" />
  404. <Icon icon="fa:bars" class="icon" />
  405. </el-button>
  406. </div>
  407. </div>
  408. <el-drawer
  409. v-model="drawer"
  410. placement="right"
  411. size="80%"
  412. :with-header="false"
  413. >
  414. <div class="p-4 space-y-3">
  415. <ul class="flex flex-col gap-3 text-[#303133]">
  416. <li>
  417. <a
  418. class="hover:text-[#02409b] cursor-pointer"
  419. @click="router.push('/')"
  420. >首页</a
  421. >
  422. </li>
  423. <li>
  424. <a class="hover:text-[#02409b] cursor-pointer" @click="goFlow"
  425. >流程门户</a
  426. >
  427. </li>
  428. <li>
  429. <a class="hover:text-[#02409b] cursor-pointer" @click="goDrive"
  430. >驾驶舱门户</a
  431. >
  432. </li>
  433. <li><a class="hover:text-[#02409b] cursor-pointer">报表门户</a></li>
  434. </ul>
  435. <div class="flex items-center gap-3 mt-3">
  436. <template v-if="isLoggedIn">
  437. <el-dropdown @command="onUserCommand" trigger="click">
  438. <span class="flex items-center gap-2 cursor-pointer pr-2">
  439. <div class="avatar-wrapper">
  440. <img
  441. :src="userAvatar || person"
  442. alt="avatar"
  443. class="w-8 h-8 rounded-full avatar-image"
  444. />
  445. </div>
  446. <span class="text-sm text-[#303133]">{{ userName }}</span>
  447. </span>
  448. <template #dropdown>
  449. <el-dropdown-menu>
  450. <el-dropdown-item command="profile">
  451. <span class="pl-2">个人中心</span>
  452. </el-dropdown-item>
  453. <el-dropdown-item command="logout">
  454. <span class="pl-2">退出登录</span>
  455. </el-dropdown-item>
  456. </el-dropdown-menu>
  457. </template>
  458. </el-dropdown>
  459. </template>
  460. <el-button
  461. v-else
  462. type="primary"
  463. class="flex-1 bg-[#0050b3]!"
  464. @click="login"
  465. >登录</el-button
  466. >
  467. </div>
  468. </div>
  469. </el-drawer>
  470. </header>
  471. </template>
  472. <script setup lang="ts">
  473. import { Icon } from "@iconify/vue";
  474. import { ref, computed, onMounted, onBeforeUnmount } from "vue";
  475. import { useRouter } from "vue-router";
  476. import logo from "@/assets/images/logo.png";
  477. import person from "@/assets/images/person.png";
  478. import { useUserStoreWithOut } from "@/stores/useUserStore";
  479. const userStore = useUserStoreWithOut();
  480. import {
  481. getNotifyMessages,
  482. getNotifyMessageList,
  483. markMessageAsRead,
  484. getUnreadNotifyMessageCount,
  485. getOANotifyMessages,
  486. getOANotifyMessageList,
  487. markOAMessageAsRead,
  488. } from "@api/user";
  489. import {
  490. getAccessToken,
  491. getRefreshToken,
  492. removeToken,
  493. setToken,
  494. } from "@utils/auth";
  495. import { deleteUserCache } from "@hooks/useCache";
  496. import { manualLogoutKey, reloginCancelKey } from "@/config/axios/service";
  497. // 新增消息中心状态
  498. const activeTab = ref("messages");
  499. const messages = ref([]);
  500. const isLoggedIn = computed(
  501. () => !!userStore.isSetUser || !!userStore.user?.id,
  502. );
  503. const userAvatar = computed(() => userStore.user?.avatar || "");
  504. const userName = computed(() => userStore.user?.nickname || "");
  505. // 是否有未读消息
  506. const hasUnreadMessages = computed(() => {
  507. return messages.value.some((msg) => msg.status === "0");
  508. });
  509. // oa是否有未读
  510. const oaHasUnreadCount = computed(() => {
  511. return oaMessagesList.value.some((msg) => msg.status === "0");
  512. });
  513. // 未读消息数量
  514. const unreadMessageCount = computed(() => {
  515. return messages.value.filter((msg) => msg.status === "0").length;
  516. });
  517. // oa未读消息数量
  518. const oaUnreadCount = computed(() => {
  519. return oaMessagesList.value.filter((msg) => msg.status === "0").length;
  520. });
  521. // oa未读
  522. const oaMessagesList = ref([]);
  523. const unreadCount = ref(0); // 未读消息数量
  524. const getUnreadCount = async () => {
  525. if (!getAccessToken()) {
  526. unreadCount.value = 0;
  527. return;
  528. }
  529. const data = await getUnreadNotifyMessageCount();
  530. unreadCount.value = data;
  531. };
  532. let messageTimer: ReturnType<typeof setInterval> | undefined;
  533. let unreadTimer: ReturnType<typeof setInterval> | undefined;
  534. onMounted(async () => {
  535. if (isLoggedIn.value) {
  536. getUnreadCount();
  537. await getNotifyMessages(userStore.getUser.username);
  538. const messageList = await getNotifyMessageList(userStore.getUser.username);
  539. messages.value = messageList.filter((msg: any) => msg.status === "0");
  540. // oa消息
  541. await getOANotifyMessages(userStore.getUser.username);
  542. const oaMessageList = await getOANotifyMessageList(
  543. userStore.getUser.username,
  544. );
  545. oaMessagesList.value = oaMessageList.filter((msg) => msg.status === "0");
  546. }
  547. messageTimer = setInterval(
  548. async () => {
  549. if (isLoggedIn.value) {
  550. await getNotifyMessages(userStore.getUser.username);
  551. const messageList = await getNotifyMessageList(
  552. userStore.getUser.username,
  553. );
  554. messages.value = messageList.filter((msg: any) => msg.status === "0");
  555. // oa消息
  556. await getOANotifyMessages(userStore.getUser.username);
  557. const oaMessageList = await getOANotifyMessageList(
  558. userStore.getUser.username,
  559. );
  560. oaMessagesList.value = oaMessageList.filter(
  561. (msg: any) => msg.status === "0",
  562. );
  563. }
  564. },
  565. 1000 * 60 * 5,
  566. );
  567. unreadTimer = setInterval(
  568. () => {
  569. if (userStore.getIsSetUser && getAccessToken()) {
  570. console.log("轮询刷新小红点");
  571. getUnreadCount();
  572. } else {
  573. unreadCount.value = 0;
  574. }
  575. },
  576. 1000 * 60 * 1,
  577. );
  578. });
  579. onBeforeUnmount(() => {
  580. if (messageTimer) {
  581. clearInterval(messageTimer);
  582. }
  583. if (unreadTimer) {
  584. clearInterval(unreadTimer);
  585. }
  586. });
  587. function timestampToDateTime(timestamp) {
  588. // 兼容 10位(秒) / 13位(毫秒)
  589. const len = String(timestamp).length;
  590. const date = new Date(Number(timestamp) * (len === 10 ? 1000 : 1));
  591. // 年
  592. const year = date.getFullYear();
  593. // 月(0~11 → +1)
  594. const month = String(date.getMonth() + 1).padStart(2, "0");
  595. // 日
  596. const day = String(date.getDate()).padStart(2, "0");
  597. // 时
  598. const hours = String(date.getHours()).padStart(2, "0");
  599. // 分
  600. const minutes = String(date.getMinutes()).padStart(2, "0");
  601. // 秒
  602. const seconds = String(date.getSeconds()).padStart(2, "0");
  603. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  604. }
  605. const router = useRouter();
  606. const drawer = ref(false);
  607. // 全部标为已读
  608. const markAllAsRead = async () => {
  609. await markMessageAsRead(userStore.getUser.username);
  610. // 刷新消息列表
  611. const messageList = await getNotifyMessageList(userStore.getUser.username);
  612. messages.value = messageList.filter((msg: any) => msg.status === "0");
  613. };
  614. // oa全部标为已读
  615. const oaMarkAllAsRead = async () => {
  616. await markOAMessageAsRead(userStore.getUser.username);
  617. // 刷新消息列表
  618. const messageList = await getOANotifyMessageList(userStore.getUser.username);
  619. oaMessagesList.value = messageList.filter((msg: any) => msg.status === "0");
  620. };
  621. const goHome = () => {
  622. router.push({ path: "/" });
  623. };
  624. const login = () => {
  625. router.push({
  626. path: "/login",
  627. });
  628. };
  629. const goFlow = () => {
  630. router.push({ path: "/flow" });
  631. };
  632. const goDrive = () => {
  633. router.push({ path: "/drive" });
  634. };
  635. const onUserCommand = async (command: string) => {
  636. if (command === "logout") {
  637. // await userStore.loginOut();
  638. deleteUserCache(); // 删除用户缓存
  639. sessionStorage.setItem(manualLogoutKey, "true");
  640. sessionStorage.removeItem(reloginCancelKey);
  641. removeToken();
  642. window.location.href = "/login";
  643. }
  644. };
  645. </script>
  646. <style scoped>
  647. .avatar-wrapper {
  648. position: relative;
  649. overflow: hidden;
  650. border-radius: 50%;
  651. }
  652. .avatar-wrapper::before {
  653. content: "";
  654. position: absolute;
  655. top: 0;
  656. left: -100%;
  657. width: 50%;
  658. height: 100%;
  659. background: linear-gradient(
  660. 90deg,
  661. rgba(255, 255, 255, 0) 0%,
  662. rgba(255, 255, 255, 0.8) 50%,
  663. rgba(255, 255, 255, 0) 100%
  664. );
  665. transform: skewX(-25deg);
  666. transition: none;
  667. z-index: 1;
  668. opacity: 0;
  669. }
  670. .avatar-wrapper:hover::before {
  671. animation: shine 0.5s ease-out;
  672. }
  673. @keyframes shine {
  674. 0% {
  675. left: -100%;
  676. opacity: 0;
  677. }
  678. 10% {
  679. opacity: 1;
  680. }
  681. 100% {
  682. left: 100%;
  683. opacity: 0;
  684. }
  685. }
  686. .notification-dropdown {
  687. width: 400px !important;
  688. max-height: 500px;
  689. overflow: hidden;
  690. }
  691. .notification-tabs .el-tabs__header {
  692. margin-bottom: 0;
  693. padding: 10px;
  694. background-color: #f8f9fa;
  695. }
  696. .tab-content {
  697. max-height: 400px;
  698. overflow-y: auto;
  699. padding: 10px;
  700. }
  701. .message-item {
  702. display: flex;
  703. align-items: flex-start;
  704. padding: 12px 8px;
  705. border-bottom: 1px solid #eee;
  706. }
  707. .message-item:last-child {
  708. border-bottom: none;
  709. }
  710. .message-icon {
  711. margin-right: 12px;
  712. display: flex;
  713. align-items: center;
  714. }
  715. .message-text {
  716. flex: 1;
  717. }
  718. .message-title {
  719. font-weight: 500;
  720. color: #303133;
  721. margin-bottom: 4px;
  722. }
  723. .message-desc {
  724. font-size: 13px;
  725. color: #909399;
  726. line-height: 1.4;
  727. margin-bottom: 4px;
  728. }
  729. .message-time {
  730. font-size: 12px;
  731. color: #c0c4cc;
  732. }
  733. .no-messages,
  734. .no-tasks {
  735. text-align: center;
  736. padding: 20px;
  737. color: #909399;
  738. font-style: italic;
  739. }
  740. .task-item {
  741. display: flex;
  742. align-items: center;
  743. justify-content: space-between;
  744. padding: 12px 8px;
  745. border-bottom: 1px solid #eee;
  746. }
  747. .task-item:last-child {
  748. border-bottom: none;
  749. }
  750. .task-info {
  751. flex: 1;
  752. }
  753. .task-title {
  754. font-weight: 500;
  755. color: #303133;
  756. margin-bottom: 4px;
  757. }
  758. .task-desc {
  759. font-size: 13px;
  760. color: #909399;
  761. line-height: 1.4;
  762. margin-bottom: 4px;
  763. }
  764. .task-time {
  765. font-size: 12px;
  766. color: #c0c4cc;
  767. }
  768. </style>