328 lines
9.3 KiB
Vue
328 lines
9.3 KiB
Vue
<template>
|
|
<div
|
|
class="notice"
|
|
v-show="visible"
|
|
:style="{
|
|
transform: show ? 'scaleY(1)' : 'scaleY(0.9)',
|
|
opacity: show ? 1 : 0
|
|
}"
|
|
@click.stop=""
|
|
>
|
|
<div class="header">
|
|
<!-- <span class="text">{{ $t('notice.title') }}</span> -->
|
|
<span class="text">消息</span>
|
|
<span class="btn" @click="readAll">{{ $t('notice.btnRead') }}</span>
|
|
</div>
|
|
<ul class="bar">
|
|
<li
|
|
v-for="(item, index) in barList"
|
|
:key="index"
|
|
:class="{ active: barActiveIndex === index }"
|
|
>
|
|
{{ item.name }} ({{ noticeList.length }})
|
|
</li>
|
|
</ul>
|
|
<div class="content">
|
|
<div class="scroll">
|
|
<!-- 通知 -->
|
|
<ul class="notice-list">
|
|
<!-- <ul class="notice-list" v-show="barActiveIndex === 0"> -->
|
|
<li v-for="(item, index) in noticeList" :key="index" @click="showfn(item)" v-ripple>
|
|
<div
|
|
class="icon"
|
|
:style="{
|
|
background: getNoticeStyle(item.message.scene).backgroundColor + '!important'
|
|
}"
|
|
>
|
|
<i
|
|
class="iconfont-sys"
|
|
:style="{ color: getNoticeStyle(item.message.scene).iconColor + '!important' }"
|
|
v-html="getNoticeStyle(item.message.scene).icon"
|
|
>
|
|
</i>
|
|
</div>
|
|
<div class="text">
|
|
<h4>{{ item.message.title }}</h4>
|
|
<p>{{ item.message.created_at }}</p>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
<!-- 消息 -->
|
|
<!-- <ul class="user-list" v-show="barActiveIndex === 1">
|
|
<li v-for="(item, index) in msgList" :key="index">
|
|
<div class="avatar">
|
|
<img :src="item.avatar" />
|
|
</div>
|
|
<div class="text">
|
|
<h4>{{ item.title }}</h4>
|
|
<p>{{ item.time }}</p>
|
|
</div>
|
|
</li>
|
|
</ul> -->
|
|
<!-- 待办 -->
|
|
<!-- <ul class="base" v-show="barActiveIndex === 3">
|
|
<li v-for="(item, index) in pendingList" :key="index">
|
|
<h4>{{ item.title }}</h4>
|
|
<p>{{ item.time }}</p>
|
|
</li>
|
|
</ul> -->
|
|
|
|
<div class="empty-tips" v-show="noticeList.length === 0">
|
|
<i class="iconfont-sys"></i>
|
|
<p>{{ $t('notice.text[0]') }}{{ barList[barActiveIndex].name }}</p>
|
|
</div>
|
|
<!-- <div class="empty-tips" v-show="barActiveIndex === 1 && msgList.length === 0">
|
|
<i class="iconfont-sys"></i>
|
|
<p>{{ $t('notice.text[0]') }}{{ barList[barActiveIndex].name }}</p>
|
|
</div>
|
|
<div class="empty-tips" v-show="barActiveIndex === 2 && pendingList.length === 0">
|
|
<i class="iconfont-sys"></i>
|
|
<p>{{ $t('notice.text[0]') }}{{ barList[barActiveIndex].name }}</p>
|
|
</div> -->
|
|
</div>
|
|
<div class="btn-wrapper">
|
|
<el-button class="view-all" @click="handleViewAll" v-ripple>
|
|
{{ $t('notice.viewAll') }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="height: 100px"></div>
|
|
</div>
|
|
<!-- 全局通知详情弹窗 -->
|
|
<Teleport to="body">
|
|
<ElDialog v-model="dialogVisible" title="通知详情" align-center :append-to-body="true" width="640px">
|
|
<div class="detail-dialog">
|
|
<div class="section">
|
|
<div class="info-grid">
|
|
<div class="label">发送人</div>
|
|
<div class="value">{{ noticeDetail.sender_name || '-' }}</div>
|
|
</div>
|
|
<div class="info-grid">
|
|
<div class="label">通知标题</div>
|
|
<div class="value">{{ noticeDetail.title || '-' }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="section">
|
|
<div class="label" style="margin-bottom: 8px">通知内容</div>
|
|
<div class="content-box">
|
|
<div class="message-text" v-html="noticeDetail.content"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ElDialog>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import avatar1 from '@/assets/img/avatar/avatar1.webp'
|
|
import avatar2 from '@/assets/img/avatar/avatar2.webp'
|
|
import avatar3 from '@/assets/img/avatar/avatar3.webp'
|
|
import avatar4 from '@/assets/img/avatar/avatar4.webp'
|
|
import avatar5 from '@/assets/img/avatar/avatar5.webp'
|
|
import avatar6 from '@/assets/img/avatar/avatar6.webp'
|
|
import AppConfig from '@/config'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { commonService } from '@/api/commonApi'
|
|
import { mittBus } from '@/utils/sys'
|
|
const dialogVisible = ref(false)
|
|
const router = useRouter()
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps({
|
|
value: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
watch(
|
|
() => props.value,
|
|
() => {
|
|
showNotice(props.value)
|
|
}
|
|
)
|
|
|
|
const show = ref(false)
|
|
const visible = ref(false)
|
|
const barActiveIndex = ref(0)
|
|
const pendingList: any = []
|
|
|
|
const barList = ref([
|
|
{
|
|
name: computed(() => t('notice.bar[0]')),
|
|
num: 1
|
|
}
|
|
])
|
|
const noticeList = ref<any>([])
|
|
const getNoticeList = async () => {
|
|
await commonService.getUnReadNoticeList().then((res: any) => {
|
|
noticeList.value = res.list
|
|
// 通知头部组件更新铃铛状态
|
|
mittBus.emit('notification-data-changed', res.list?.length || 0)
|
|
})
|
|
}
|
|
onMounted(() => {
|
|
getNoticeList()
|
|
// 监听WebSocket消息:收到新通知实时插入到列表首位
|
|
mittBus.on('ws:message', onWsMessage)
|
|
})
|
|
onUnmounted(() => {
|
|
mittBus.off('ws:message')
|
|
})
|
|
|
|
const onWsMessage = (data: any) => {
|
|
if (data && data.event === 'notify.receive') {
|
|
// 将新通知插入到列表首位。后端数据结构需包含 type 与 message 字段
|
|
// 若结构不同,可在此处做一次字段映射
|
|
console.log(data)
|
|
noticeList.value.unshift(data.data.notice_data)
|
|
console.log(noticeList.value)
|
|
mittBus.emit('notification-data-changed', noticeList.value.length)
|
|
}
|
|
}
|
|
// const changeBar = (index: number) => {
|
|
// barActiveIndex.value = index
|
|
// }
|
|
|
|
const getRandomColor = () => {
|
|
const index = Math.floor(Math.random() * AppConfig.systemMainColor.length)
|
|
return AppConfig.systemMainColor[index]
|
|
}
|
|
|
|
const noticeStyleMap = {
|
|
4: {
|
|
icon: '',
|
|
iconColor: 'rgb(var(--art-warning))',
|
|
backgroundColor: 'rgb(var(--art-bg-warning))'
|
|
},
|
|
2: {
|
|
icon: '',
|
|
iconColor: 'rgb(var(--art-success))',
|
|
backgroundColor: 'rgb(var(--art-bg-success))'
|
|
},
|
|
5: {
|
|
icon: '',
|
|
iconColor: 'rgb(var(--art-danger))',
|
|
backgroundColor: 'rgb(var(--art-bg-danger))'
|
|
},
|
|
3: {
|
|
icon: '',
|
|
iconColor: 'rgb(var(--art-info))',
|
|
backgroundColor: 'rgb(var(--art-bg-info))'
|
|
},
|
|
1: {
|
|
icon: '',
|
|
iconColor: 'rgb(var(--art-primary))',
|
|
backgroundColor: 'rgb(var(--art-bg-primary))'
|
|
}
|
|
}
|
|
|
|
const getNoticeStyle = (type: string) => {
|
|
const defaultStyle = {
|
|
icon: '',
|
|
iconColor: '#FFFFFF',
|
|
backgroundColor: getRandomColor()
|
|
}
|
|
|
|
// 先转成 unknown,再转成 keyof 类型,避免 TS 报错
|
|
const key = type as unknown as keyof typeof noticeStyleMap
|
|
const style = noticeStyleMap[key] || defaultStyle
|
|
|
|
// const style = noticeStyleMap[type as keyof typeof noticeStyleMap] || defaultStyle
|
|
|
|
return {
|
|
...style,
|
|
backgroundColor: style.backgroundColor
|
|
}
|
|
}
|
|
|
|
const showNotice = (open: boolean) => {
|
|
if (open) {
|
|
visible.value = open
|
|
setTimeout(() => {
|
|
show.value = open
|
|
}, 5)
|
|
} else {
|
|
show.value = open
|
|
setTimeout(() => {
|
|
visible.value = open
|
|
}, 350)
|
|
}
|
|
}
|
|
const readAll = async () => {
|
|
const { message } = await commonService.readNotice({
|
|
msg_ids: 'all'
|
|
})
|
|
ElMessage.success(message)
|
|
getNoticeList()
|
|
}
|
|
// 查看全部
|
|
const handleViewAll = () => {
|
|
handleNoticeAll()
|
|
}
|
|
const noticeDetail = ref<any>({})
|
|
const showfn = async (row: any) => {
|
|
dialogVisible.value = true
|
|
await commonService.getNoticeDetail({ id: row.id }).then((res: any) => {
|
|
noticeDetail.value = res.message
|
|
showNotice(false) // 关闭通知组件
|
|
getNoticeList()
|
|
})
|
|
}
|
|
const handleNoticeAll = () => {
|
|
router.push('/common/notice')
|
|
showNotice(false) // 关闭通知组件
|
|
}
|
|
const handleMsgAll = () => {}
|
|
const handlePendingAll = () => {}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@use './style';
|
|
.el-button {
|
|
--el-button-border-color: #dcdfe6 !important;
|
|
--el-button-text-color: var(--el-text-color-regular);
|
|
}
|
|
.message-text {
|
|
padding: 10px 14px;
|
|
line-height: 1.7;
|
|
white-space: pre-line;
|
|
word-break: break-word;
|
|
}
|
|
.detail-dialog {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
.section {
|
|
background: var(--el-fill-color-blank);
|
|
border: 1px solid var(--el-border-color-light);
|
|
border-radius: 8px;
|
|
padding: 12px 14px;
|
|
}
|
|
.info-grid {
|
|
display: grid;
|
|
grid-template-columns: 96px 1fr;
|
|
gap: 6px 12px;
|
|
align-items: center;
|
|
padding: 6px 0;
|
|
}
|
|
.label {
|
|
color: var(--el-text-color-secondary);
|
|
font-size: 13px;
|
|
}
|
|
.value {
|
|
color: var(--el-text-color-primary);
|
|
font-size: 14px;
|
|
}
|
|
.content-box {
|
|
background: var(--el-fill-color-lighter);
|
|
border: 1px dashed var(--el-border-color);
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
max-height: 320px;
|
|
overflow: auto;
|
|
}
|
|
</style>
|