客户超额消息通知
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<el-dialog v-model="notifyState.visible" :title="dialogTitle" width="660px" append-to-body
|
||||
:close-on-click-modal="false" class="notify-popup-dialog" @open="loadList">
|
||||
<div class="np-toolbar">
|
||||
<el-radio-group v-model="readFilter" size="small" @change="loadList">
|
||||
<el-radio-button value="">全部</el-radio-button>
|
||||
<el-radio-button value="0">未读</el-radio-button>
|
||||
<el-radio-button value="1">已读</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" link :disabled="!notifyState.unreadCount" @click="handleReadAll">全部标记已读</el-button>
|
||||
</div>
|
||||
<el-scrollbar max-height="420px">
|
||||
<div v-for="item in list" :key="item.receiveId" class="np-item" :class="{ unread: item.readFlag === '0' }">
|
||||
<div class="np-item-head">
|
||||
<el-badge is-dot :hidden="item.readFlag === '1'" type="danger">
|
||||
<span class="np-title">{{ item.title }}</span>
|
||||
</el-badge>
|
||||
<span class="np-time">{{ parseTime(item.createTime) }}</span>
|
||||
</div>
|
||||
<div class="np-content">{{ item.content }}</div>
|
||||
<div class="np-foot">
|
||||
<el-tag v-if="item.eventType === 'CUSTOMER_OVERDUE'" type="danger" size="small">客户已超额</el-tag>
|
||||
<span class="np-amount" v-if="item.overdueAmount">超额金额:{{ formatMoney(item.overdueAmount) }} CNY</span>
|
||||
<el-button v-if="item.readFlag === '0'" link type="primary" @click="handleRead(item)">标记已读</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="!list.length" :description="readFilter === '0' ? '暂无未读通知' : '暂无通知'" :image-size="80" />
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="notifyState.visible = false">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="NotifyPopup">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { popupNotify, listMyNotify, countMyNotify, readMyNotify, readAllMyNotify } from '@/api/system/notify'
|
||||
import { notifyState } from './notifyState'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const parseTime = proxy.parseTime
|
||||
const formatMoney = (v) => (v === null || v === undefined || v === '' ? '0.00' : Number(v).toFixed(2))
|
||||
|
||||
const list = ref([])
|
||||
const readFilter = ref('')
|
||||
const autoOpened = ref(false)
|
||||
const dialogTitle = computed(() => '我的通知' + (notifyState.unreadCount > 0 ? `(${notifyState.unreadCount} 条未读)` : ''))
|
||||
|
||||
function refreshCount() {
|
||||
countMyNotify().then(res => { notifyState.unreadCount = Number(res.data) || 0 }).catch(() => {})
|
||||
}
|
||||
|
||||
function loadList() {
|
||||
listMyNotify(readFilter.value).then(res => { list.value = res.data || [] })
|
||||
}
|
||||
|
||||
function handleRead(item) {
|
||||
readMyNotify(item.receiveId).then(() => {
|
||||
item.readFlag = '1'
|
||||
refreshCount()
|
||||
})
|
||||
}
|
||||
|
||||
function handleReadAll() {
|
||||
readAllMyNotify().then(() => {
|
||||
proxy.$modal.msgSuccess('已全部标记为已读')
|
||||
refreshCount()
|
||||
loadList()
|
||||
})
|
||||
}
|
||||
|
||||
/** 对外暴露手动打开入口 */
|
||||
notifyState.open = () => {
|
||||
readFilter.value = ''
|
||||
notifyState.visible = true
|
||||
}
|
||||
notifyState.refreshCount = refreshCount
|
||||
notifyState.loadList = loadList
|
||||
|
||||
/**
|
||||
* 登录后自动弹屏:
|
||||
* 1) 先让路由/首页渲染稳定;
|
||||
* 2) 若“初始/过期密码修改”提示正在展示(user.js 写 session 标记),等待其关闭后再弹,避免双框冲突;
|
||||
* 3) 待弹屏消息只弹一次(后端拉取即置 popup_flag=Y),关闭后可从头像菜单“我的通知”再次查看。
|
||||
*/
|
||||
function waitPasswordPromptThenRun(run) {
|
||||
const START_DELAY = 1200
|
||||
const POLL_INTERVAL = 800
|
||||
const MAX_WAIT = 60000
|
||||
setTimeout(() => {
|
||||
const begin = Date.now()
|
||||
const timer = setInterval(() => {
|
||||
const pwdShowing = sessionStorage.getItem('bmsPwdPrompt') === '1'
|
||||
if (!pwdShowing) {
|
||||
clearInterval(timer)
|
||||
setTimeout(run, 300)
|
||||
} else if (Date.now() - begin > MAX_WAIT) {
|
||||
// 用户长时间未处理改密框,超时也弹出通知,避免消息永远看不到
|
||||
clearInterval(timer)
|
||||
run()
|
||||
}
|
||||
}, POLL_INTERVAL)
|
||||
}, START_DELAY)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshCount()
|
||||
waitPasswordPromptThenRun(() => {
|
||||
if (autoOpened.value) return
|
||||
popupNotify().then(res => {
|
||||
if ((res.data || []).length) {
|
||||
autoOpened.value = true
|
||||
readFilter.value = ''
|
||||
notifyState.visible = true
|
||||
refreshCount()
|
||||
}
|
||||
}).catch(() => {})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.np-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.np-item {
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
background: var(--el-fill-color-blank);
|
||||
|
||||
&.unread {
|
||||
border-left: 3px solid var(--el-color-danger);
|
||||
background: var(--el-color-danger-light-9);
|
||||
}
|
||||
}
|
||||
.np-item-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.np-title {
|
||||
font-weight: 600;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.np-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.np-content {
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.np-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.np-amount {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
/**
|
||||
* 通知弹屏共享状态(单例):
|
||||
* - 组件挂载于全局 Layout,负责登录后自动拉取待弹屏消息
|
||||
* - Navbar “我的通知”入口通过 open() 打开同一弹窗
|
||||
*/
|
||||
export const notifyState = reactive({
|
||||
visible: false,
|
||||
unreadCount: 0
|
||||
})
|
||||
Reference in New Issue
Block a user