wujingjing
2025-04-02 2c65df541b7a6ed14b5dd42235cea535de0f0758
src/layout/component/header/Header.vue
@@ -1,58 +1,171 @@
<template>
   <div class="top_text flex justify-between px-6 items-center">
      <div v-if="routerMeta.showTitle" class="font-bold flex items-center cursor-pointer" @click="goBack">
         <span class="flex-center">
            <SvgIcon name="ele-ArrowLeft" />
         </span>
         <span class="text-sm">
            {{ routerMeta.title }}
         </span>
      </div>
      <div class="notice">
         <el-badge :value="`${state.announcementList.length}`">
            <el-button link size="small" icon="ele-Message" class="set-notice" @click="handleAnnouncementClick">系统公告</el-button>
         </el-badge>
         <div class="notice_box notice_box_show" v-show="state.isShowAnnouncement">
            <div class="notice_box_header">
               <span>最新公告</span>
            </div>
            <div class="notice_box_body">
               <div class="notice_item" v-for="item in state.announcementList" :key="item.notify_id">
                  <p>{{ item.notify_message }}</p>
                  <p class="text-right mr-[23px]">
                     <span>{{ item.notify_time }}</span>
                  </p>
               </div>
            </div>
   <div class="top_text flex justify-between px-6 items-center" :class="sidebarIsShow ? 'px-6' : 'pl-[unset] pr-6'">
      <div class="flex-items-center h-full">
         <div class="nav-menu">
            <router-link :to="firstToPath" class="nav-item" active-class="active">
               <i class="icon-park-outline-robot"></i>
               智能助手
            </router-link>
            <router-link to="/workspace/situation" class="nav-item" active-class="active">
               <i class="icon-park-outline-workbench"></i>
               个人工作台
            </router-link>
            <router-link to="/gis/situation" class="nav-item" active-class="active">
               <i class="icon-park-outline-system"></i>
               GIS系统
            </router-link>
         </div>
      </div>
      <el-dialog
         v-model="state.isAnnouncementDialog"
         width="500"
         :before-close="handleCloseAnnouncement"
         :modal="false"
         title="公告内容"
         :align-center="true"
      >
         <div class="set-content">
            <span class="notice-content">{{ state.announcementContent }}</span>
         </div>
         <template #footer>
            <p class="text-right text-[#555]">
               <span>{{ state.announcementTime }}</span>
            </p>
         </template>
      </el-dialog>
      <el-tooltip content="最小化" placement="bottom">
         <span class="cursor-pointer ywifont ywicon-tuichuquanping" size="15px" @click="smallScreenClick" />
      </el-tooltip>
   </div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive } from 'vue';
import { onClickOutside } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue';
import { systemNotifyList } from '/@/api/ai/chat';
import router from '/@/router';
import pinia from '/@/stores';
import { activeChatRoom, activeRoomId, isSharePage, newChatRoomClick } from '/@/stores/chatRoom';
import { useThemeConfig } from '/@/stores/themeConfig';
import emitter from '/@/utils/mitt';
import { userInfoKey } from '/@/utils/request';
import { Local } from '/@/utils/storage';
import { ParentRegister } from '/@/stores/global';
const props = defineProps(['sidebarIsShow']);
let state = reactive({
   isShowAnnouncement: false,
   isAnnouncementDialog: false,
   announcementList: [],
   announcementContent: '',
   announcementTime: '',
});
const smallScreenClick = () => {
   ParentRegister.notify?.({
      type: 'fullScreen',
      value: false,
   });
};
const firstToPath = computed(() => {
   if (!activeChatRoom.value)
      return {
         path: '/home',
      };
   const result = activeChatRoom.value.isInitial
      ? {
            path: '/home',
            query: {
               id: activeChatRoom.value.id,
            },
        }
      : {
            path: '/ask_answer',
            query: { id: activeChatRoom.value.id },
        };
   return result;
});
//#region ====================== 公告是否看过 ======================
const userInfo = ref(Local.get(userInfoKey));
const readKey = `announcementIsRead_${userInfo.value?.id}`;
const getIsRead = () => {
   if (!userInfo.value?.id) return false;
   const isRead = Local.get(readKey) ?? false;
   return isRead;
};
const setRead = (isRead: boolean) => {
   if (!userInfo.value?.id) return;
   announcementIsRead.value = isRead;
   Local.set(readKey, isRead);
};
const announcementIsRead = ref(getIsRead());
//#endregion
const noticeRef = ref(null);
const getSystemNotify = async () => {
   const res = await systemNotifyList();
   state.announcementList = res.messages || [];
   res.messages?.forEach((element) => {
      element.notify_time = element.notify_time.slice(0, 10);
   });
   state.announcementList = res.messages?.sort(sortData).slice(0, 5) ?? [];
};
const routerMeta = computed(() => router.currentRoute.value.meta);
const stores = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(stores);
const globalTitle = computed(() => themeConfig.value.globalTitle);
const setHeaderTitle = (title: string) => {
   document.title = `${title} - ${globalTitle.value}`;
};
const handleAnnouncementClick = () => {
   state.isShowAnnouncement = !state.isShowAnnouncement;
   if (!announcementIsRead.value && state.isShowAnnouncement) {
      setRead(true);
   }
};
const goBack = () => {
   router.back();
};
//根据指定字段 规则排序 这里是获取时间的时间戳然后比较
function sortData(a, b) {
   return new Date(b.notify_time).getTime() - new Date(a.notify_time).getTime();
}
//公告内容点击事件
const announcementContentClick = (item) => {
   state.announcementContent = item.notify_message;
   state.announcementTime = item.notify_time.slice(0, 10);
   state.isAnnouncementDialog = true;
};
const handleCloseAnnouncement = () => {
   state.isAnnouncementDialog = false;
};
// 区域关闭最新公告
onClickOutside(
   noticeRef,
   () => {
      state.isShowAnnouncement = false;
   },
   {
      ignore: ['.el-overlay-dialog'],
   }
);
const newChatClick = () => {
   newChatRoomClick();
};
onMounted(() => {
   getSystemNotify();
   emitter.on('updateHeaderTitle', setHeaderTitle);
});
onUnmounted(() => {
   emitter.off('updateHeaderTitle', setHeaderTitle);
});
</script>
<style scoped lang="scss">
@@ -61,24 +174,26 @@
   height: 42px;
   background-color: #fff;
   top: 0;
   z-index: 10;
   z-index: 1;
}
.notice {
   position: fixed;
   top: 18px;
   top: 12px;
   right: 30px;
   z-index: 12;
   z-index: 1;
   .set-notice {
      font-size: 12px;
      font-weight: 400;
      letter-spacing: 0;
      line-height: 17.38px;
      color: #9598b3;
      z-index: 0;
   }
   .notice_box_show {
      width: 300px !important;
      height: 400px !important;
      height: 470px !important;
      // height: 100% !important;
      padding: 0 20px 10px;
      ::-webkit-scrollbar {
         height: 0;
@@ -88,7 +203,7 @@
   }
   .notice_box {
      position: absolute;
      z-index: 12;
      z-index: 1;
      top: calc(100% + 20px);
      right: -10px;
      width: 0;
@@ -111,18 +226,84 @@
      }
      &_body {
         height: calc(100% - 40px);
         overflow: auto;
         // overflow: auto;
         .notice_item {
            cursor: pointer;
            padding: 10px;
            width: 280px;
            width: 272px;
            border-top: 1px solid #efefef;
            color: #767a97;
            position: relative;
            box-sizing: border-box;
            line-height: 19px;
            font-size: 12px;
            .set-circle {
               width: 3px;
               height: 3px;
               position: absolute;
               top: 17px;
               left: 0;
               transform: scale(0.8) translate(50%, -50%);
               display: block;
               padding: 2px;
               min-width: 3px;
               min-height: 3px;
               text-align: center;
               border-radius: 50%;
               background: #ff423d;
               color: #fff;
               font-size: 12px;
            }
         }
      }
   }
}
.set-content {
   padding: 0px 20px;
   .notice-content {
      white-space: pre-wrap;
      font-size: 14px;
   }
}
:deep(.el-dialog__footer) {
   border-top: unset;
   padding: 10px 20px 20px;
   box-sizing: border-box;
}
.nav-menu {
   display: flex;
   align-items: center;
   height: 100%;
   gap: 8px;
   .nav-item {
      display: flex;
      align-items: center;
      padding: 0 16px;
      height: 100%;
      color: var(--el-text-color-regular);
      text-decoration: none;
      font-size: 14px;
      transition: all 0.3s ease;
      border-bottom: 2px solid transparent;
      gap: 4px;
      i {
         font-size: 16px;
      }
      &:hover {
         color: var(--el-color-primary);
         background-color: rgba(var(--el-color-primary-rgb), 0.1);
      }
      &.active {
         color: var(--el-color-primary);
         border-bottom-color: var(--el-color-primary);
         background-color: rgba(var(--el-color-primary-rgb), 0.1);
      }
   }
}
</style>