Header.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <template>
  2. <header class="z-40" :class="{ dark: store.state.semidark && store.state.menu === 'horizontal' }">
  3. <div class="shadow-sm">
  4. <div class="relative bg-white flex w-full items-center px-5 py-2.5 dark:bg-[#0e1726]">
  5. <div class="horizontal-logo flex lg:hidden justify-between items-center ltr:mr-2 rtl:ml-2">
  6. <router-link to="/dashboard" class="main-logo flex items-center shrink-0">
  7. <img class="w-full h-12 ltr:-ml-1 rtl:-mr-1 inline" src="https://www.raycos.com.cn/theme/m049/images/logo.png" alt="" />
  8. <!-- <span class="text-2xl ltr:ml-1.5 rtl:mr-1.5 font-semibold align-middle hidden md:inline dark:text-white-light transition-all duration-300"
  9. >V3studio App</span
  10. > -->
  11. </router-link>
  12. <!-- 原导航 -->
  13. <a
  14. href="javascript:;"
  15. class="collapse-icon flex-none dark:text-[#d0d2d6] hover:text-primary dark:hover:text-primary flex lg:hidden ltr:ml-2 rtl:mr-2 p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
  16. @click="toggleSidebars"
  17. >
  18. <icon-menu class="w-5 h-5" />
  19. </a>
  20. </div>
  21. <!-- 头部中间功能--待开发 -->
  22. <div class="ltr:mr-2 rtl:ml-2 hidden sm:block">
  23. <ul class="flex items-center space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]">
  24. <li>
  25. <!-- to="/apps/calendar" -->
  26. <router-link
  27. to=""
  28. class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  29. >
  30. <icon-calendar />
  31. </router-link>
  32. </li>
  33. <li>
  34. <router-link
  35. to=""
  36. class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  37. >
  38. <icon-edit />
  39. </router-link>
  40. </li>
  41. <li>
  42. <router-link
  43. to=""
  44. class="block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  45. >
  46. <icon-chat-notification />
  47. </router-link>
  48. </li>
  49. </ul>
  50. </div>
  51. <div
  52. class="sm:flex-1 ltr:sm:ml-0 ltr:ml-auto sm:rtl:mr-0 rtl:mr-auto flex items-center space-x-1.5 lg:space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]"
  53. >
  54. <div class="sm:ltr:mr-auto sm:rtl:ml-auto">
  55. <form
  56. class="sm:relative absolute inset-x-0 sm:top-0 top-1/2 sm:translate-y-0 -translate-y-1/2 sm:mx-0 mx-4 z-10 sm:block hidden"
  57. :class="{ '!block': search }"
  58. @submit.prevent="search = false"
  59. ></form>
  60. <button
  61. type="button"
  62. class="search_btn sm:hidden p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:bg-white-light/90 dark:hover:bg-dark/60"
  63. @click="search = !search"
  64. >
  65. <icon-search class="w-4.5 h-4.5 mx-auto dark:text-[#d0d2d6]" />
  66. </button>
  67. </div>
  68. <a href="javascript:;" v-if="superMenu == 'compan' && founder == '1'" @click="getBranch('super')"> 返回</a>
  69. <div>
  70. <a
  71. href="javascript:;"
  72. v-show="store.state.theme === 'light'"
  73. class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  74. @click="toggleTheme('dark')"
  75. >
  76. <icon-sun />
  77. </a>
  78. <a
  79. href="javascript:;"
  80. v-show="store.state.theme === 'dark'"
  81. class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  82. @click="toggleTheme('system')"
  83. >
  84. <icon-moon />
  85. </a>
  86. <a
  87. href="javascript:;"
  88. v-show="store.state.theme === 'system'"
  89. class="flex items-center p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  90. @click="toggleTheme('dark')"
  91. >
  92. <!-- <icon-laptop /> -->
  93. <icon-sun />
  94. </a>
  95. </div>
  96. <div class="dropdown shrink-0 mr-2">
  97. <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-start' : 'bottom-end'" offsetDistance="0">
  98. <el-badge :value="countNot" :hidden="countNot == 0" class="item">
  99. <button
  100. type="button"
  101. class="pt-2 block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60"
  102. @click="goNotify"
  103. >
  104. <icon-mail-dot />
  105. </button>
  106. </el-badge>
  107. <!-- <template #content="{ close }">
  108. <ul class="top-11 !py-0 text-dark dark:text-white-dark w-[300px] sm:w-[375px] text-xs">
  109. <li class="mb-5">
  110. <div class="overflow-hidden relative rounded-t-md !p-5 text-white">
  111. <div class="absolute h-full w-full bg-[url('/assets/images/menu-heade.jpg')] bg-no-repeat bg-center bg-cover inset-0"></div>
  112. <h4 class="font-semibold relative z-10 text-lg">Messages</h4>
  113. </div>
  114. </li>
  115. <template v-for="msg in messages" :key="msg.id">
  116. <li>
  117. <div class="flex items-center py-3 px-5">
  118. <div v-html="msg.image"></div>
  119. <span class="px-3 dark:text-gray-500">
  120. <div class="font-semibold text-sm dark:text-white-light/90" v-text="msg.title"></div>
  121. <div v-text="msg.message"></div>
  122. </span>
  123. <span
  124. class="font-semibold bg-white-dark/20 rounded text-dark/60 px-1 ltr:ml-auto rtl:mr-auto whitespace-pre dark:text-white-dark ltr:mr-2 rtl:ml-2"
  125. v-text="msg.time"
  126. ></span>
  127. <button type="button" class="text-neutral-300 hover:text-danger" @click="removeMessage(msg.id)">
  128. <icon-x-circle />
  129. </button>
  130. </div>
  131. </li>
  132. </template>
  133. <template v-if="messages.length">
  134. <li class="border-t border-white-light text-center dark:border-white/10 mt-5">
  135. <div
  136. class="flex items-center py-4 px-5 text-primary font-semibold group dark:text-gray-400 justify-center cursor-pointer"
  137. @click="close()"
  138. >
  139. <span class="group-hover:underline ltr:mr-1 rtl:ml-1">VIEW ALL ACTIVITIES</span>
  140. <icon-arrow-left class="group-hover:translate-x-1 transition duration-300 ltr:ml-1 rtl:mr-1" />
  141. </div>
  142. </li>
  143. </template>
  144. <template v-if="!messages.length">
  145. <li class="mb-5">
  146. <div class="!grid place-content-center hover:!bg-transparent text-lg min-h-[200px]">
  147. <div class="mx-auto ring-4 ring-primary/30 rounded-full mb-4 text-primary">
  148. <icon-info-circle :fill="true" class="w-10 h-10" />
  149. </div>
  150. No data available.
  151. </div>
  152. </li>
  153. </template>
  154. </ul>
  155. </template> -->
  156. </Popper>
  157. </div>
  158. <!-- <div class="dropdown shrink-0">
  159. <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'"
  160. offsetDistance="8">
  161. <button type="button"
  162. class="relative block p-2 rounded-full bg-white-light/40 dark:bg-dark/40 hover:text-primary hover:bg-white-light/90 dark:hover:bg-dark/60">
  163. <icon-bell-bing />
  164. <span class="flex absolute w-3 h-3 ltr:right-0 rtl:left-0 top-0">
  165. <span
  166. class="animate-ping absolute ltr:-left-[3px] rtl:-right-[3px] -top-[3px] inline-flex h-full w-full rounded-full bg-success/50 opacity-75"></span>
  167. <span class="relative inline-flex rounded-full w-[6px] h-[6px] bg-success"></span>
  168. </span>
  169. </button>
  170. <template #content="{ close }">
  171. <ul
  172. class="!py-0 text-dark dark:text-white-dark w-[300px] sm:w-[350px] divide-y dark:divide-white/10">
  173. <li>
  174. <div class="flex items-center px-4 py-2 justify-between font-semibold">
  175. <h4 class="text-lg">Notification</h4>
  176. <template v-if="notifications.length">
  177. <span class="badge bg-primary/80"
  178. v-text="notifications.length + 'New'"></span>
  179. </template>
  180. </div>
  181. </li>
  182. <template v-for="notification in notifications" :key="notification.id">
  183. <li class="dark:text-white-light/90">
  184. <div class="group flex items-center px-4 py-2">
  185. <div class="grid place-content-center rounded">
  186. <div class="w-12 h-12 relative">
  187. <img class="w-12 h-12 rounded-full object-cover"
  188. :src="`/assets/images/${notification.profile}`" alt="" />
  189. <span
  190. class="bg-success w-2 h-2 rounded-full block absolute right-[6px] bottom-0"></span>
  191. </div>
  192. </div>
  193. <div class="ltr:pl-3 rtl:pr-3 flex flex-auto">
  194. <div class="ltr:pr-3 rtl:pl-3">
  195. <h6 v-html="notification.message"></h6>
  196. <span class="text-xs block font-normal dark:text-gray-500"
  197. v-text="notification.time"></span>
  198. </div>
  199. <button type="button"
  200. class="ltr:ml-auto rtl:mr-auto text-neutral-300 hover:text-danger opacity-0 group-hover:opacity-100"
  201. @click="removeNotification(notification.id)">
  202. <icon-x-circle />
  203. </button>
  204. </div>
  205. </div>
  206. </li>
  207. </template>
  208. <template v-if="notifications.length">
  209. <li>
  210. <div class="p-4">
  211. <button class="btn btn-primary block w-full btn-small"
  212. @click="close()">Read All Notifications</button>
  213. </div>
  214. </li>
  215. </template>
  216. <template v-if="!notifications.length">
  217. <li>
  218. <div
  219. class="!grid place-content-center hover:!bg-transparent text-lg min-h-[200px]">
  220. <div
  221. class="mx-auto ring-4 ring-primary/30 rounded-full mb-4 text-primary">
  222. <icon-info-circle :fill="true" class="w-10 h-10" />
  223. </div>
  224. No data available.
  225. </div>
  226. </li>
  227. </template>
  228. </ul>
  229. </template>
  230. </Popper>
  231. </div> -->
  232. <div class="dropdown shrink-0">
  233. <Popper :placement="store.state.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'" offsetDistance="8" class="!block">
  234. <button type="button" class="relative group block">
  235. <img
  236. class="w-9 h-9 rounded-full object-cover saturate-50 group-hover:saturate-100"
  237. v-if="store.state.user.user.face !== undefined"
  238. :src="store.state.user.user.face"
  239. alt=""
  240. />
  241. <img class="w-9 h-9 rounded-full object-cover saturate-50 group-hover:saturate-100" v-else src="../../assets/images/logos.png" alt="" />
  242. </button>
  243. <template #content="{ close }">
  244. <ul class="text-dark dark:text-white-dark !py-0 w-[230px] font-semibold dark:text-white-light/90">
  245. <li>
  246. <div class="flex items-center px-4 py-4">
  247. <div class="flex-none">
  248. <img
  249. class="rounded-md w-10 h-10 object-cover"
  250. v-if="store.state.user.user.face !== undefined"
  251. :src="store.state.user.user.face"
  252. alt=""
  253. />
  254. <img class="rounded-md w-10 h-10 object-cover" v-else src="../../assets/images/logos.png" alt="" />
  255. </div>
  256. <div class="ltr:pl-4 rtl:pr-4 truncate">
  257. <h4 class="text-base">
  258. {{ store.state.user.user.username }}<span class="text-xs bg-success-light rounded text-success px-1 ltr:ml-2 rtl:ml-2">Pro</span>
  259. </h4>
  260. <!-- <a class="text-black/60 hover:text-primary dark:text-dark-light/60 dark:hover:text-white" href="javascript:;">johndoe@gmail.com</a> -->
  261. </div>
  262. </div>
  263. </li>
  264. <li>
  265. <a href="javascript:;" class="dark:hover:text-white" @click="goInfo">
  266. <icon-user class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 shrink-0" />
  267. 个人信息
  268. </a>
  269. </li>
  270. <li>
  271. <a href="javascript:;" class="dark:hover:text-white" @click="editPassword">
  272. <icon-lock-dots class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 shrink-0" />
  273. 修改密码
  274. </a>
  275. </li>
  276. <li class="border-t border-white-light dark:border-white-light/10">
  277. <a class="text-danger !py-3 dark:hover:text-white" @click="handleLogoutClick">
  278. <icon-logout class="w-4.5 h-4.5 ltr:mr-2 rtl:ml-2 rotate-90 shrink-0" />
  279. 退出
  280. </a>
  281. </li>
  282. </ul>
  283. </template>
  284. </Popper>
  285. </div>
  286. </div>
  287. </div>
  288. </div>
  289. </header>
  290. </template>
  291. <script lang="ts" setup>
  292. import { ref, onMounted, computed, reactive, watch } from 'vue';
  293. import { useI18n } from 'vue-i18n';
  294. import appSetting from '@/app-setting';
  295. import { useRouter, useRoute } from 'vue-router';
  296. import { useStore } from 'vuex';
  297. import IconMenu from '@/components/icon/icon-menu.vue';
  298. import IconCalendar from '@/components/icon/icon-calendar.vue';
  299. import IconEdit from '@/components/icon/icon-edit.vue';
  300. import IconChatNotification from '@/components/icon/icon-chat-notification.vue';
  301. import IconSearch from '@/components/icon/icon-search.vue';
  302. import IconXCircle from '@/components/icon/icon-x-circle.vue';
  303. import IconSun from '@/components/icon/icon-sun.vue';
  304. import IconMoon from '@/components/icon/icon-moon.vue';
  305. import IconLaptop from '@/components/icon/icon-laptop.vue';
  306. import IconMailDot from '@/components/icon/icon-mail-dot.vue';
  307. import IconArrowLeft from '@/components/icon/icon-arrow-left.vue';
  308. /* import IconInfoCircle from '@/components/icon/icon-info-circle.vue';
  309. import IconBellBing from '@/components/icon/icon-bell-bing.vue';
  310. import IconUser from '@/components/icon/icon-user.vue';
  311. import IconMail from '@/components/icon/icon-mail.vue';
  312. import IconLockDots from '@/components/icon/icon-lock-dots.vue';
  313. import IconLogout from '@/components/icon/icon-logout.vue'; */
  314. import IconMenuDashboard from '@/components/icon/menu/icon-menu-dashboard.vue';
  315. import IconCaretDown from '@/components/icon/icon-caret-down.vue';
  316. import IconMenuApps from '@/components/icon/menu/icon-menu-apps.vue';
  317. import IconMenuComponents from '@/components/icon/menu/icon-menu-components.vue';
  318. import IconMenuElements from '@/components/icon/menu/icon-menu-elements.vue';
  319. import IconMenuDatatables from '@/components/icon/menu/icon-menu-datatables.vue';
  320. import IconMenuForms from '@/components/icon/menu/icon-menu-forms.vue';
  321. import IconMenuPages from '@/components/icon/menu/icon-menu-pages.vue';
  322. import IconMenuMore from '@/components/icon/menu/icon-menu-more.vue';
  323. import { ElMessageBox } from 'element-plus';
  324. import * as API_Company from '@/api/company';
  325. //ElMessageBox
  326. const store = useStore();
  327. const route = useRoute();
  328. const router = useRouter();
  329. const search = ref(false);
  330. // multi language
  331. const i18n = reactive(useI18n());
  332. const changeLanguage = (item: any) => {
  333. i18n.locale = item.code;
  334. appSetting.toggleLanguage(item);
  335. };
  336. const superMenu = computed(() => store.state.superMenu);
  337. const founder = computed(() => store.state.user.user.founder);
  338. const currentFlag = computed(() => {
  339. return `/assets/images/flags/${i18n.locale.toUpperCase()}.svg`;
  340. });
  341. const countNot = ref(0);
  342. /* 获取账号未读消息 */
  343. const GET_countNot = () => {
  344. API_Company.countNotReadSys().then((res) => {
  345. countNot.value = res;
  346. });
  347. };
  348. const notifications = ref([
  349. {
  350. id: 1,
  351. profile: 'user-profile.jpeg',
  352. message: '<strong class="text-sm mr-1">John Doe</strong>invite you to <strong>Prototyping</strong>',
  353. time: '45 min ago',
  354. },
  355. {
  356. id: 2,
  357. profile: 'profile-34.jpeg',
  358. message: '<strong class="text-sm mr-1">Adam Nolan</strong>mentioned you to <strong>UX Basics</strong>',
  359. time: '9h Ago',
  360. },
  361. {
  362. id: 3,
  363. profile: 'profile-16.jpeg',
  364. message: '<strong class="text-sm mr-1">Anna Morgan</strong>Upload a file',
  365. time: '9h Ago',
  366. },
  367. ]);
  368. const messages = ref([
  369. {
  370. id: 1,
  371. image:
  372. '<span class="grid place-content-center w-9 h-9 rounded-full bg-success-light dark:bg-success text-success dark:text-success-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span>',
  373. title: 'Congratulations!',
  374. message: 'Your OS has been updated.',
  375. time: '1hr',
  376. },
  377. {
  378. id: 2,
  379. image:
  380. '<span class="grid place-content-center w-9 h-9 rounded-full bg-info-light dark:bg-info text-info dark:text-info-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>',
  381. title: 'Did you know?',
  382. message: 'You can switch between artboards.',
  383. time: '2hr',
  384. },
  385. {
  386. id: 3,
  387. image:
  388. '<span class="grid place-content-center w-9 h-9 rounded-full bg-danger-light dark:bg-danger text-danger dark:text-danger-light"> <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></span>',
  389. title: 'Something went wrong!',
  390. message: 'Send Reposrt',
  391. time: '2days',
  392. },
  393. {
  394. id: 4,
  395. image:
  396. '<span class="grid place-content-center w-9 h-9 rounded-full bg-warning-light dark:bg-warning text-warning dark:text-warning-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span>',
  397. title: 'Warning',
  398. message: 'Your password strength is low.',
  399. time: '5days',
  400. },
  401. ]);
  402. onMounted(() => {
  403. setActiveDropdown();
  404. GET_countNot();
  405. });
  406. watch(route, (to, from) => {
  407. setActiveDropdown();
  408. });
  409. const setActiveDropdown = () => {
  410. const selector = document.querySelector('ul.horizontal-menu a[href="' + window.location.pathname + '"]');
  411. if (selector) {
  412. selector.classList.add('active');
  413. const all: any = document.querySelectorAll('ul.horizontal-menu .nav-link.active');
  414. for (let i = 0; i < all.length; i++) {
  415. all[0]?.classList.remove('active');
  416. }
  417. const ul: any = selector.closest('ul.sub-menu');
  418. if (ul) {
  419. let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link');
  420. if (ele) {
  421. ele = ele[0];
  422. setTimeout(() => {
  423. ele?.classList.add('active');
  424. });
  425. }
  426. }
  427. }
  428. };
  429. /* 前往消息页面 */
  430. const goNotify = () => {
  431. router.push({
  432. name: 'Notify',
  433. });
  434. };
  435. /* 修改密码 */
  436. const editPassword = () => {
  437. router.push({
  438. path: '/lockscreen',
  439. });
  440. };
  441. /* 前往个人信息页面 */
  442. const goInfo = () => {
  443. router.push({
  444. name: 'companyInfo',
  445. });
  446. };
  447. const removeNotification = (value: number) => {
  448. notifications.value = notifications.value.filter((d) => d.id !== value);
  449. };
  450. const removeMessage = (value: number) => {
  451. messages.value = messages.value.filter((d) => d.id !== value);
  452. };
  453. const toggleSidebars = () => {
  454. store.dispatch('toggleSidebar');
  455. };
  456. const toggleTheme = (theme) => {
  457. store.dispatch('toggleTheme', theme);
  458. };
  459. const closes = () => {
  460. console.log('001');
  461. };
  462. const handleLogoutClick = () => {
  463. ElMessageBox.confirm('确定退出吗?', '提示', {
  464. confirmButtonText: '确定',
  465. cancelButtonText: '取消',
  466. type: 'warning',
  467. })
  468. .then(() => {
  469. store.dispatch('logoutAction').then(() => {
  470. const route = router.resolve('/login');
  471. location.replace(route.href); // 为了重新实例化vue-router对象 避免bug
  472. });
  473. })
  474. .catch(() => {
  475. /* Handle cancel action if needed */
  476. });
  477. };
  478. const getBranch = (value) => {
  479. store.dispatch('toggleSuperMenu', value);
  480. router.push({
  481. name: 'dashboard',
  482. });
  483. };
  484. </script>