mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-11-04 10:57:08 +08:00 
			
		
		
		
	refactor: 优化消息通知
This commit is contained in:
		@@ -14,6 +14,6 @@ export function deleteMessage(ids: string | Array<string>) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** @desc 标记已读 */
 | 
			
		||||
export function readMessage(ids: string | Array<string>) {
 | 
			
		||||
export function readMessage(ids?: string | Array<string>) {
 | 
			
		||||
  return http.patch(`${BASE_URL}/read`, ids)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,3 @@ export function updateNotice(data: any, id: string) {
 | 
			
		||||
export function deleteNotice(ids: string | Array<number>) {
 | 
			
		||||
  return http.del(`${BASE_URL}/${ids}`)
 | 
			
		||||
}
 | 
			
		||||
export function getNoticeList(type: string | Array<number>) {
 | 
			
		||||
  return http.get(`/system/message?page=1&size=10&sort=createTime,desc&isRead=false&type=${type}`)
 | 
			
		||||
}
 | 
			
		||||
export function endAllMessage() {
 | 
			
		||||
  return http.patch(`/system/message/read`)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -316,8 +316,8 @@ export interface MessageResp {
 | 
			
		||||
  content: string
 | 
			
		||||
  type: number
 | 
			
		||||
  isRead: boolean
 | 
			
		||||
  readTime: string
 | 
			
		||||
  createUserString: string
 | 
			
		||||
  readTime?: string
 | 
			
		||||
  createUserString?: string
 | 
			
		||||
  createTime: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="message">
 | 
			
		||||
    <a-tabs default-active-key="1">
 | 
			
		||||
      <a-tab-pane key="1">
 | 
			
		||||
        <template #title>通知({{ props.list.length }})</template>
 | 
			
		||||
        <section>
 | 
			
		||||
          <a-comment v-for="item in props.list" :key="item.id"
 | 
			
		||||
                     :author="item.createUserString" :content="item.content" :datetime="item.createTime">
 | 
			
		||||
            <template #actions></template>
 | 
			
		||||
          </a-comment>
 | 
			
		||||
          <Empty :fetch="props.fetch" :list="props.list" />
 | 
			
		||||
        </section>
 | 
			
		||||
      </a-tab-pane>
 | 
			
		||||
      <a-tab-pane key="2">
 | 
			
		||||
        <template #title>关注({{ props.follwlist?.length || 0 }})</template>
 | 
			
		||||
        <section>
 | 
			
		||||
          <template v-if="props.follwlist.length > 0">
 | 
			
		||||
            <a-comment v-for="(item, index) in props.follwlist" :key="index"
 | 
			
		||||
                       :author="item.createUserString" :content="item.content" :datetime="item.createTime">
 | 
			
		||||
              <template #actions></template>
 | 
			
		||||
            </a-comment>
 | 
			
		||||
          </template>
 | 
			
		||||
          <Empty :fetch="props.fetch" :list="props.follwlist" />
 | 
			
		||||
        </section>
 | 
			
		||||
      </a-tab-pane>
 | 
			
		||||
      <a-tab-pane key="3">
 | 
			
		||||
        <template #title>待办({{ props.todulist?.length || 0 }})</template>
 | 
			
		||||
 | 
			
		||||
        <section>
 | 
			
		||||
          <template v-if="props.todulist.length > 0">
 | 
			
		||||
            <a-comment v-for="(item, index) in props.todulist" :key="index"
 | 
			
		||||
                       :author="item.createUserString" :content="item.content" :datetime="item.createTime">
 | 
			
		||||
              <template #actions></template>
 | 
			
		||||
            </a-comment>
 | 
			
		||||
          </template>
 | 
			
		||||
          <Empty :fetch="props.fetch" :list="props.todulist" />
 | 
			
		||||
        </section>
 | 
			
		||||
      </a-tab-pane>
 | 
			
		||||
    </a-tabs>
 | 
			
		||||
    <a-list>
 | 
			
		||||
      <template #header>通知</template>
 | 
			
		||||
      <a-list-item v-for="item in props.data" :key="item.id">
 | 
			
		||||
        <div class="content-wrapper" @click="open">
 | 
			
		||||
          <div class="content">{{ item.title }}</div>
 | 
			
		||||
          <div class="date">{{ item.createTime }}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </a-list-item>
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <a class="more-btn" @click="open">查看更多
 | 
			
		||||
          <icon-right />
 | 
			
		||||
        </a>
 | 
			
		||||
        <a class="read-all-btn" @click="readAll">全部已读</a>
 | 
			
		||||
      </template>
 | 
			
		||||
    </a-list>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import Empty from './empty.vue'
 | 
			
		||||
import { readMessage } from '@/apis'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'Message' })
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  list: {
 | 
			
		||||
    type: Array as PropType<any>, // 简化数据结构以便测试
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  todulist: {
 | 
			
		||||
    type: Array as PropType<any>, // 简化数据结构以便测试
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  follwlist: {
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Array as PropType<any>, // 简化数据结构以便测试
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
@@ -62,6 +31,78 @@ const props = defineProps({
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 打开消息中心
 | 
			
		||||
const open = () => {
 | 
			
		||||
  window.open('/#/setting/message')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 全部已读
 | 
			
		||||
const readAll = async () => {
 | 
			
		||||
  await readMessage()
 | 
			
		||||
  props.fetch()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.message {
 | 
			
		||||
  height: auto;
 | 
			
		||||
  max-height: calc(100% - 51px);
 | 
			
		||||
  width: 300px;
 | 
			
		||||
 | 
			
		||||
  .content-wrapper {
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border-radius: var(--border-radius-medium);
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    .content {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      height: 20px;
 | 
			
		||||
      max-width: 265px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
      width: 265px;
 | 
			
		||||
    }
 | 
			
		||||
    .date {
 | 
			
		||||
      color: var(--color-text-4);
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      margin-top: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: var(--color-bg-4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:active {
 | 
			
		||||
      color: rgb(var(--arcoblue-6));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :deep(.arco-list) {
 | 
			
		||||
    border-radius: var(--border-radius-medium);
 | 
			
		||||
    .arco-list-header {
 | 
			
		||||
      font-size: 13px;
 | 
			
		||||
      padding: 9px 12px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .arco-list-content {
 | 
			
		||||
      max-height: 184px;
 | 
			
		||||
 | 
			
		||||
      .arco-list-item {
 | 
			
		||||
        padding: 6px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .arco-list-footer {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      color: rgb(var(--arcoblue-6));
 | 
			
		||||
      padding: 9px 12px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
 | 
			
		||||
      .more-btn {
 | 
			
		||||
        margin-right: auto;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 Before Width: | Height: | Size: 10 KiB  | 
@@ -1,61 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-empty v-if="props.list.length === 0">
 | 
			
		||||
    <template #image>
 | 
			
		||||
      <img src="./emp.svg" />
 | 
			
		||||
    </template>
 | 
			
		||||
    暂无数据
 | 
			
		||||
  </a-empty>
 | 
			
		||||
  <div class="footer">
 | 
			
		||||
    <a class="more_btn" @click="open">查看更多 ></a>
 | 
			
		||||
    <a class="end_all_btn" @click="endAll">一键已读</a>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { Message } from '@arco-design/web-vue'
 | 
			
		||||
import { endAllMessage } from '@/apis'
 | 
			
		||||
 | 
			
		||||
// 定义组件名称
 | 
			
		||||
defineOptions({ name: 'Empty' })
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  list: {
 | 
			
		||||
    type: Array as PropType<any>, // 简化数据结构以便测试
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  fetch: {
 | 
			
		||||
    type: Function, // 简化数据结构以便测试
 | 
			
		||||
    required: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const open = () => {
 | 
			
		||||
  window.open('/#/setting/message')
 | 
			
		||||
}
 | 
			
		||||
const endAll = async () => {
 | 
			
		||||
  const response = await endAllMessage('1')
 | 
			
		||||
  if (response?.code === 200) {
 | 
			
		||||
    props.fetch()
 | 
			
		||||
    Message.success('全部已读')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.footer {
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  padding: 0px 5px;
 | 
			
		||||
 | 
			
		||||
  .more_btn {
 | 
			
		||||
    color: rgb(0, 119, 255);
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .end_all_btn {
 | 
			
		||||
    color: rgb(0, 119, 255);
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -11,16 +11,21 @@
 | 
			
		||||
      </a-tooltip>
 | 
			
		||||
 | 
			
		||||
      <!-- 消息通知 -->
 | 
			
		||||
      <a-popover position="bottom">
 | 
			
		||||
        <a-badge :count="list.length > 0 || todulist.length > 0 || follwlist.length > 0 ? 9 : 0" dot>
 | 
			
		||||
          <a-button size="mini" class="gi_hover_btn" @click="handleClick">
 | 
			
		||||
      <a-popover
 | 
			
		||||
        position="bottom"
 | 
			
		||||
        trigger="click"
 | 
			
		||||
        :content-style="{ marginTop: '-5px', padding: 0, border: 'none' }"
 | 
			
		||||
        :arrow-style="{ width: 0, height: 0 }"
 | 
			
		||||
      >
 | 
			
		||||
        <a-badge :count="messageData.length" dot>
 | 
			
		||||
          <a-button size="mini" class="gi_hover_btn">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <icon-notification :size="18" />
 | 
			
		||||
            </template>
 | 
			
		||||
          </a-button>
 | 
			
		||||
        </a-badge>
 | 
			
		||||
        <template #content>
 | 
			
		||||
          <Message :fetch="fetchData" :list="list" :follwlist="follwlist" :todulist="todulist"></Message>
 | 
			
		||||
          <Message :fetch="getMessageData" :data="messageData" />
 | 
			
		||||
        </template>
 | 
			
		||||
      </a-popover>
 | 
			
		||||
 | 
			
		||||
@@ -69,73 +74,65 @@
 | 
			
		||||
import { Modal } from '@arco-design/web-vue'
 | 
			
		||||
import { useFullscreen } from '@vueuse/core'
 | 
			
		||||
import { onMounted, ref } from 'vue'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
import SettingDrawer from './SettingDrawer.vue'
 | 
			
		||||
import dayjs from 'dayjs'
 | 
			
		||||
import Message from './Message.vue'
 | 
			
		||||
import SettingDrawer from './SettingDrawer.vue'
 | 
			
		||||
import { listMessage } from '@/apis'
 | 
			
		||||
import { useUserStore } from '@/stores'
 | 
			
		||||
import { isMobile } from '@/utils'
 | 
			
		||||
import { getToken } from '@/utils/auth'
 | 
			
		||||
import { getNoticeList } from '@/apis'
 | 
			
		||||
 | 
			
		||||
defineOptions({ name: 'HeaderRight' })
 | 
			
		||||
let socket: WebSocket
 | 
			
		||||
const sys = 'SYSTEM'
 | 
			
		||||
const list = ref<Array<any>>([])
 | 
			
		||||
const follwlist = ref<Array<any>>([])
 | 
			
		||||
const todulist = ref<Array<any>>([])
 | 
			
		||||
const hover = ref<boolean>(false)
 | 
			
		||||
 | 
			
		||||
let socket: WebSocket
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  if (socket) {
 | 
			
		||||
    socket.close()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const messageData = ref<Array<any>>([])
 | 
			
		||||
const createWebSocket = (token) => {
 | 
			
		||||
  socket = new WebSocket(`${import.meta.env.VITE_API_WS_URL}/ws?token=${token}`)
 | 
			
		||||
 | 
			
		||||
  socket.onopen = () => {
 | 
			
		||||
    console.log('WebSocket connection opened')
 | 
			
		||||
    // console.log('WebSocket connection opened')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  socket.onmessage = (event) => {
 | 
			
		||||
    // fetchData()
 | 
			
		||||
    const data = JSON.parse(event.data)
 | 
			
		||||
    // const data = messageData.content
 | 
			
		||||
    console.log(data)
 | 
			
		||||
    if (data.msgType === 1) {
 | 
			
		||||
      list.value.unshift({
 | 
			
		||||
        createUserString: data.fromName,
 | 
			
		||||
        content: data?.content,
 | 
			
		||||
        createTime:
 | 
			
		||||
          data?.sendTime
 | 
			
		||||
          && moment(data.sendTime).format('YYYY-MM-DD HH:mm:ss')
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    messageData.value.unshift({
 | 
			
		||||
      id: data?.id,
 | 
			
		||||
      title: data?.content,
 | 
			
		||||
      type: data?.type,
 | 
			
		||||
      isRead: data?.isRead,
 | 
			
		||||
      createTime:
 | 
			
		||||
        data?.sendTime
 | 
			
		||||
        && dayjs(data.sendTime).format('YYYY-MM-DD HH:mm:ss')
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  socket.onerror = (error) => {
 | 
			
		||||
    console.error('WebSocket error:', error)
 | 
			
		||||
  socket.onerror = () => {
 | 
			
		||||
    // console.error('WebSocket error:', error)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  socket.onclose = () => {
 | 
			
		||||
    console.log('WebSocket connection closed')
 | 
			
		||||
    // console.log('WebSocket connection closed')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchData = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    const token = getToken()
 | 
			
		||||
    const response = await getNoticeList('1')
 | 
			
		||||
    list.value = response?.data?.list || []
 | 
			
		||||
    if (token) {
 | 
			
		||||
      createWebSocket(token)
 | 
			
		||||
    }
 | 
			
		||||
    const follwResponse = await getNoticeList('2')
 | 
			
		||||
    const toduResponse = await getNoticeList('3')
 | 
			
		||||
    follwlist.value = follwResponse?.data?.list || []
 | 
			
		||||
    todulist.value = toduResponse?.data?.list || []
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error fetching data:', error)
 | 
			
		||||
// 查询消息通知
 | 
			
		||||
const queryMessageParam = reactive({
 | 
			
		||||
  isRead: false,
 | 
			
		||||
  sort: ['createTime,desc'],
 | 
			
		||||
  page: 1,
 | 
			
		||||
  size: 5
 | 
			
		||||
})
 | 
			
		||||
const getMessageData = async () => {
 | 
			
		||||
  const token = getToken()
 | 
			
		||||
  const { data } = await listMessage(queryMessageParam)
 | 
			
		||||
  messageData.value = data.list
 | 
			
		||||
  if (token) {
 | 
			
		||||
    createWebSocket(token)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -190,11 +187,8 @@ const checkPasswordExpired = () => {
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkPasswordExpired()
 | 
			
		||||
  fetchData()
 | 
			
		||||
  getMessageData()
 | 
			
		||||
})
 | 
			
		||||
const handleClick = () => {
 | 
			
		||||
  window.open('/#/setting/message')
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -149,10 +149,12 @@
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: var(--color-secondary-hover) !important;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:active {
 | 
			
		||||
    background: var(--color-secondary-active) !important;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user