header.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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="w-full 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-20 h-9" />
  10. <span class="text-[#02409b] text-[20px] font-bold border-l-2 pl-1"
  11. >DeepOil</span
  12. >
  13. </div>
  14. <nav class="hidden lg:flex flex-1 mx-4 ml-10 text-sm">
  15. <ul class="flex items-center gap-6 text-[#303133] text-lg">
  16. <li><a class="hover:text-[#02409b] cursor-pointer">产品</a></li>
  17. <li><a class="hover:text-[#02409b] cursor-pointer">解决方案</a></li>
  18. <li><a class="hover:text-[#02409b] cursor-pointer">典型案例</a></li>
  19. <li><a class="hover:text-[#02409b] cursor-pointer">平台服务</a></li>
  20. <li>
  21. <a class="hover:text-[#02409b] cursor-pointer" @click="goFlow"
  22. >流程门户</a
  23. >
  24. </li>
  25. </ul>
  26. </nav>
  27. <div class="hidden lg:flex items-center gap-3 h-full">
  28. <!-- 消息中心 -->
  29. <el-dropdown trigger="click" placement="bottom-end">
  30. <div class="flex items-center gap-2 cursor-pointer pr-2">
  31. <Icon
  32. icon="mdi:bell"
  33. class="w-5 h-5 text-gray-600 hover:text-[#409EFF]"
  34. />
  35. <span class="text-sm text-gray-600">消息代办</span>
  36. </div>
  37. <template #dropdown>
  38. <el-dropdown-menu class="notification-dropdown">
  39. <div class="notification-tabs pl-2">
  40. <el-tabs v-model="activeTab" class="demo-tabs">
  41. <el-tab-pane label="消息中心" name="messages">
  42. <div class="tab-content">
  43. <!-- 消息中心内容 -->
  44. <div
  45. class="message-item"
  46. v-for="(item, index) in messages"
  47. :key="index"
  48. >
  49. <div class="message-icon">
  50. <Icon
  51. :icon="item.icon"
  52. class="w-5 h-5"
  53. :class="item.typeClass"
  54. />
  55. </div>
  56. <div class="message-text">
  57. <p class="message-title">{{ item.title }}</p>
  58. <p class="message-desc">{{ item.desc }}</p>
  59. <p class="message-time">{{ item.time }}</p>
  60. </div>
  61. </div>
  62. <div v-if="!messages.length" class="no-messages">
  63. 暂无新消息
  64. </div>
  65. </div>
  66. </el-tab-pane>
  67. <el-tab-pane label="待办任务" name="tasks">
  68. <div class="tab-content">
  69. <!-- 待办任务内容 -->
  70. <div
  71. class="task-item"
  72. v-for="(task, index) in tasks"
  73. :key="index"
  74. >
  75. <div class="task-info">
  76. <p class="task-title">{{ task.title }}</p>
  77. <p class="task-desc">{{ task.desc }}</p>
  78. <p class="task-time">截止时间: {{ task.dueTime }}</p>
  79. </div>
  80. <el-tag :type="task.priorityTag" size="small">{{
  81. task.priorityText
  82. }}</el-tag>
  83. </div>
  84. <div v-if="!tasks.length" class="no-tasks">
  85. 暂无待办任务
  86. </div>
  87. </div>
  88. </el-tab-pane>
  89. </el-tabs>
  90. </div>
  91. </el-dropdown-menu>
  92. </template>
  93. </el-dropdown>
  94. <template v-if="isLoggedIn">
  95. <el-dropdown @command="onUserCommand" trigger="click">
  96. <span class="flex items-center gap-2 cursor-pointer pr-2">
  97. <div class="avatar-wrapper">
  98. <img
  99. :src="userAvatar || person"
  100. alt="avatar"
  101. class="w-8 h-8 rounded-full avatar-image"
  102. />
  103. </div>
  104. <span class="text-sm text-[#303133]">{{ userName }}</span>
  105. </span>
  106. <template #dropdown>
  107. <el-dropdown-menu>
  108. <el-dropdown-item command="profile">
  109. <svg
  110. xmlns="http://www.w3.org/2000/svg"
  111. width="18"
  112. height="18"
  113. viewBox="0 0 16 16"
  114. >
  115. <g fill="none">
  116. <path
  117. fill="url(#SVG3BqCJdyi)"
  118. 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"
  119. />
  120. <path
  121. fill="url(#SVGfKhxtenh)"
  122. 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"
  123. />
  124. <path
  125. fill="url(#SVGJYCMTblH)"
  126. d="M8 1.5A2.75 2.75 0 1 1 8 7a2.75 2.75 0 0 1 0-5.5"
  127. />
  128. <defs>
  129. <linearGradient
  130. id="SVG3BqCJdyi"
  131. x1="5.378"
  132. x2="7.616"
  133. y1="8.798"
  134. y2="14.754"
  135. gradientUnits="userSpaceOnUse"
  136. >
  137. <stop offset=".125" stop-color="#9c6cfe" />
  138. <stop offset="1" stop-color="#7a41dc" />
  139. </linearGradient>
  140. <linearGradient
  141. id="SVGfKhxtenh"
  142. x1="8"
  143. x2="11.164"
  144. y1="7.286"
  145. y2="17.139"
  146. gradientUnits="userSpaceOnUse"
  147. >
  148. <stop stop-color="#885edb" stop-opacity="0" />
  149. <stop offset="1" stop-color="#e362f8" />
  150. </linearGradient>
  151. <linearGradient
  152. id="SVGJYCMTblH"
  153. x1="6.558"
  154. x2="9.361"
  155. y1="2.231"
  156. y2="6.707"
  157. gradientUnits="userSpaceOnUse"
  158. >
  159. <stop offset=".125" stop-color="#9c6cfe" />
  160. <stop offset="1" stop-color="#7a41dc" />
  161. </linearGradient>
  162. </defs>
  163. </g>
  164. </svg>
  165. <span class="pl-2">个人中心</span>
  166. </el-dropdown-item>
  167. <el-dropdown-item command="logout">
  168. <svg
  169. xmlns="http://www.w3.org/2000/svg"
  170. width="18"
  171. height="18"
  172. viewBox="0 0 24 24"
  173. >
  174. <g fill="none">
  175. <path
  176. fill="url(#SVG0pAmxd9w)"
  177. d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2"
  178. />
  179. <path
  180. fill="url(#SVGFnXqmeDt)"
  181. 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"
  182. />
  183. <defs>
  184. <linearGradient
  185. id="SVG0pAmxd9w"
  186. x1="5.125"
  187. x2="18.25"
  188. y1="3.25"
  189. y2="22.625"
  190. gradientUnits="userSpaceOnUse"
  191. >
  192. <stop stop-color="#f83f54" />
  193. <stop offset="1" stop-color="#ca2134" />
  194. </linearGradient>
  195. <linearGradient
  196. id="SVGFnXqmeDt"
  197. x1="8.685"
  198. x2="12.591"
  199. y1="12.332"
  200. y2="16.392"
  201. gradientUnits="userSpaceOnUse"
  202. >
  203. <stop stop-color="#fdfdfd" />
  204. <stop offset="1" stop-color="#fecbe6" />
  205. </linearGradient>
  206. </defs>
  207. </g>
  208. </svg>
  209. <span class="pl-2">退出登录</span>
  210. </el-dropdown-item>
  211. </el-dropdown-menu>
  212. </template>
  213. </el-dropdown>
  214. </template>
  215. <template v-else>
  216. <div
  217. class="bg-[#0050b3] hover:bg-[#0050b3]/90 text-white text-sm flex items-center justify-center cursor-pointer h-full px-10 py-4"
  218. @click="login"
  219. >
  220. 登 录
  221. </div>
  222. </template>
  223. </div>
  224. <div class="lg:hidden">
  225. <el-button link @click="drawer = true">
  226. <i class="el-icon" />
  227. <Icon icon="fa:bars" class="icon" />
  228. </el-button>
  229. </div>
  230. </div>
  231. <el-drawer
  232. v-model="drawer"
  233. placement="right"
  234. size="80%"
  235. :with-header="false"
  236. >
  237. <div class="p-4 space-y-3">
  238. <ul class="flex flex-col gap-3 text-[#303133]">
  239. <li><a class="block py-2">产品</a></li>
  240. <li><a class="block py-2">解决方案</a></li>
  241. <li><a class="block py-2">典型案例</a></li>
  242. <li><a class="block py-2">平台服务</a></li>
  243. <li><a class="block py-2">应用市场</a></li>
  244. <li><a class="block py-2">开源社区</a></li>
  245. </ul>
  246. <div class="flex items-center gap-3 mt-3">
  247. <template v-if="isLoggedIn">
  248. <el-dropdown @command="onUserCommand" trigger="click">
  249. <span class="flex items-center gap-2 cursor-pointer pr-2">
  250. <div class="avatar-wrapper">
  251. <img
  252. :src="userAvatar || person"
  253. alt="avatar"
  254. class="w-8 h-8 rounded-full avatar-image"
  255. />
  256. </div>
  257. <span class="text-sm text-[#303133]">{{ userName }}</span>
  258. </span>
  259. <template #dropdown>
  260. <el-dropdown-menu>
  261. <el-dropdown-item command="profile">
  262. <span class="pl-2">个人中心</span>
  263. </el-dropdown-item>
  264. <el-dropdown-item command="logout">
  265. <span class="pl-2">退出登录</span>
  266. </el-dropdown-item>
  267. </el-dropdown-menu>
  268. </template>
  269. </el-dropdown>
  270. </template>
  271. <el-button
  272. v-else
  273. type="primary"
  274. class="flex-1 bg-[#0050b3]!"
  275. @click="login"
  276. >登录</el-button
  277. >
  278. </div>
  279. </div>
  280. </el-drawer>
  281. </header>
  282. </template>
  283. <script setup lang="ts">
  284. import { Icon } from "@iconify/vue";
  285. import { ref, computed } from "vue";
  286. import { useRouter } from "vue-router";
  287. import logo from "@/assets/images/logo.png";
  288. import person from "@/assets/images/person.png";
  289. import { useUserStoreWithOut } from "@/stores/useUserStore";
  290. const userStore = useUserStoreWithOut();
  291. import {
  292. getAccessToken,
  293. getRefreshToken,
  294. removeToken,
  295. setToken,
  296. } from "@utils/auth";
  297. import { deleteUserCache } from "@hooks/useCache";
  298. // 新增消息中心状态
  299. const activeTab = ref("messages");
  300. const messages = ref([
  301. {
  302. title: "系统通知",
  303. desc: "您的账户已成功激活",
  304. time: "10分钟前",
  305. icon: "mdi:message-text-outline",
  306. typeClass: "text-blue-500",
  307. },
  308. {
  309. title: "安全提醒",
  310. desc: "检测到异地登录行为",
  311. time: "30分钟前",
  312. icon: "mdi:security",
  313. typeClass: "text-red-500",
  314. },
  315. {
  316. title: "更新提示",
  317. desc: "系统将于今晚进行维护升级",
  318. time: "1小时前",
  319. icon: "mdi:update",
  320. typeClass: "text-green-500",
  321. },
  322. ]);
  323. const tasks = ref([
  324. {
  325. title: "审批申请",
  326. desc: "部门采购申请等待您审批",
  327. dueTime: "今天 17:00",
  328. priorityText: "高",
  329. priorityTag: "danger",
  330. },
  331. {
  332. title: "项目汇报",
  333. desc: "月度项目进度报告待提交",
  334. dueTime: "明天",
  335. priorityText: "中",
  336. priorityTag: "warning",
  337. },
  338. {
  339. title: "会议安排",
  340. desc: "准备下周团队会议材料",
  341. dueTime: "后天",
  342. priorityText: "低",
  343. priorityTag: "info",
  344. },
  345. ]);
  346. const isLoggedIn = computed(
  347. () => !!userStore.isSetUser || !!userStore.user?.id,
  348. );
  349. const userAvatar = computed(() => userStore.user?.avatar || "");
  350. const userName = computed(() => userStore.user?.nickname || "");
  351. const router = useRouter();
  352. const drawer = ref(false);
  353. const goHome = () => {
  354. router.push({ path: "/" });
  355. };
  356. const login = () => {
  357. router.push({
  358. path: "/login",
  359. });
  360. };
  361. const goFlow = () => {
  362. router.push({ path: "/flow" });
  363. };
  364. const onUserCommand = async (command: string) => {
  365. if (command === "logout") {
  366. // await userStore.loginOut();
  367. deleteUserCache(); // 删除用户缓存
  368. removeToken();
  369. window.location.reload();
  370. }
  371. };
  372. </script>
  373. <style scoped>
  374. .avatar-wrapper {
  375. position: relative;
  376. overflow: hidden;
  377. border-radius: 50%;
  378. }
  379. .avatar-wrapper::before {
  380. content: "";
  381. position: absolute;
  382. top: 0;
  383. left: -100%;
  384. width: 50%;
  385. height: 100%;
  386. background: linear-gradient(
  387. 90deg,
  388. rgba(255, 255, 255, 0) 0%,
  389. rgba(255, 255, 255, 0.8) 50%,
  390. rgba(255, 255, 255, 0) 100%
  391. );
  392. transform: skewX(-25deg);
  393. transition: none;
  394. z-index: 1;
  395. opacity: 0;
  396. }
  397. .avatar-wrapper:hover::before {
  398. animation: shine 0.5s ease-out;
  399. }
  400. @keyframes shine {
  401. 0% {
  402. left: -100%;
  403. opacity: 0;
  404. }
  405. 10% {
  406. opacity: 1;
  407. }
  408. 100% {
  409. left: 100%;
  410. opacity: 0;
  411. }
  412. }
  413. .notification-dropdown {
  414. width: 400px !important;
  415. max-height: 500px;
  416. overflow: hidden;
  417. }
  418. .notification-tabs .el-tabs__header {
  419. margin-bottom: 0;
  420. padding: 10px;
  421. background-color: #f8f9fa;
  422. }
  423. .tab-content {
  424. max-height: 400px;
  425. overflow-y: auto;
  426. padding: 10px;
  427. }
  428. .message-item {
  429. display: flex;
  430. align-items: flex-start;
  431. padding: 12px 8px;
  432. border-bottom: 1px solid #eee;
  433. }
  434. .message-item:last-child {
  435. border-bottom: none;
  436. }
  437. .message-icon {
  438. margin-right: 12px;
  439. display: flex;
  440. align-items: center;
  441. }
  442. .message-text {
  443. flex: 1;
  444. }
  445. .message-title {
  446. font-weight: 500;
  447. color: #303133;
  448. margin-bottom: 4px;
  449. }
  450. .message-desc {
  451. font-size: 13px;
  452. color: #909399;
  453. line-height: 1.4;
  454. margin-bottom: 4px;
  455. }
  456. .message-time {
  457. font-size: 12px;
  458. color: #c0c4cc;
  459. }
  460. .no-messages,
  461. .no-tasks {
  462. text-align: center;
  463. padding: 20px;
  464. color: #909399;
  465. font-style: italic;
  466. }
  467. .task-item {
  468. display: flex;
  469. align-items: center;
  470. justify-content: space-between;
  471. padding: 12px 8px;
  472. border-bottom: 1px solid #eee;
  473. }
  474. .task-item:last-child {
  475. border-bottom: none;
  476. }
  477. .task-info {
  478. flex: 1;
  479. }
  480. .task-title {
  481. font-weight: 500;
  482. color: #303133;
  483. margin-bottom: 4px;
  484. }
  485. .task-desc {
  486. font-size: 13px;
  487. color: #909399;
  488. line-height: 1.4;
  489. margin-bottom: 4px;
  490. }
  491. .task-time {
  492. font-size: 12px;
  493. color: #c0c4cc;
  494. }
  495. </style>