| | |
| | | <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="3"> |
| | | <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.ID"> |
| | | <p>{{ item.Content }}</p> |
| | | <p class="text-right"> |
| | | <span>{{ item.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, 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, newChatRoomClick, sidebarIsShow } from '/@/stores/chatRoom'; |
| | | import { ParentRegister } from '/@/stores/global'; |
| | | import { useThemeConfig } from '/@/stores/themeConfig'; |
| | | import emitter from '/@/utils/mitt'; |
| | | import { userInfoKey } from '/@/utils/request'; |
| | | import { Local } from '/@/utils/storage'; |
| | | |
| | | const props = defineProps(['sidebarIsShow']); |
| | | let state = reactive({ |
| | | isShowAnnouncement: false, |
| | | announcementList: [ |
| | | { |
| | | ID: 1, |
| | | Content: '尊敬的用户,如果遇到上传文件失败的错误(pdf、图片、聊天记录等),请刷新一下网页再操作。', |
| | | Time: '2021-08-10', |
| | | }, |
| | | { |
| | | ID: 2, |
| | | Content: '水利工程施工现场安全工作要点', |
| | | Time: '2021-08-10', |
| | | }, |
| | | { |
| | | ID: 3, |
| | | Content: '水利工程施工现场安全工作要点', |
| | | Time: '2021-08-10', |
| | | }, |
| | | ], |
| | | isAnnouncementDialog: false, |
| | | announcementList: [], |
| | | announcementContent: '', |
| | | announcementTime: '', |
| | | }); |
| | | |
| | | const smallScreenClick = () => { |
| | | const pathname = window.location.pathname; |
| | | const basePath = pathname.replace(/\/web\/index\.html$/, ''); |
| | | window.location.href = basePath || '/'; |
| | | 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(); |
| | | 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"> |
| | | .top_text { |
| | |
| | | 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; |
| | | width: 0; |
| | | color: transparent; |
| | | } |
| | | } |
| | | .notice_box { |
| | | position: absolute; |
| | | z-index: 12; |
| | | z-index: 1; |
| | | top: calc(100% + 20px); |
| | | right: -10px; |
| | | width: 0; |
| | |
| | | background: #fff; |
| | | display: block; |
| | | -webkit-transition: all 0.3s; |
| | | |
| | | -o-transition: all 0.3s; |
| | | transition: all 0.3s; |
| | | overflow: hidden; |
| | |
| | | .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> |