<template>
|
<div class="app-container">
|
<!-- 站内提醒横幅:即将开始的会议 -->
|
<el-alert
|
v-if="upcomingMeetings.length"
|
class="remind-banner"
|
type="warning"
|
:closable="false"
|
show-icon
|
>
|
<template #title>
|
会议提醒:{{ upcomingText }}
|
</template>
|
</el-alert>
|
|
<div class="tabs-wrapper">
|
<el-tabs
|
v-model="activeTab"
|
class="meeting-tabs"
|
@tab-change="handleTabChange"
|
>
|
<el-tab-pane label="会议设置" name="setting" />
|
<el-tab-pane label="会议列表" name="index" />
|
<el-tab-pane label="会议申请" name="application" />
|
<el-tab-pane label="会议审批" name="examine" />
|
<el-tab-pane label="会议发布" name="publish" />
|
<el-tab-pane label="会议总结" name="summary" />
|
</el-tabs>
|
</div>
|
|
<div class="tab-content">
|
<keep-alive>
|
<component :is="currentComponent" />
|
</keep-alive>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ElNotification } from 'element-plus'
|
import dayjs from 'dayjs'
|
import { getMeetingPublish } from '@/api/collaborativeApproval/meeting.js'
|
import MeetSetting from '../notificationManagement/meetSetting/index.vue'
|
import MeetIndex from '../notificationManagement/meetIndex/index.vue'
|
import MeetApplication from '../notificationManagement/meetApplication/index.vue'
|
import MeetExamine from '../notificationManagement/meetExamine/index.vue'
|
import MeetPublish from '../notificationManagement/meetPublish/index.vue'
|
import MeetSummary from '../notificationManagement/summary/index.vue'
|
|
const activeTab = ref('setting')
|
|
const tabComponentMap = {
|
setting: MeetSetting,
|
index: MeetIndex,
|
application: MeetApplication,
|
examine: MeetExamine,
|
publish: MeetPublish,
|
summary: MeetSummary
|
}
|
|
const currentComponent = computed(() => tabComponentMap[activeTab.value] || MeetSetting)
|
|
function handleTabChange(name) {
|
activeTab.value = name
|
}
|
|
/* ==================== 会议提醒(站内 + 浏览器/手机端弹窗) ==================== */
|
const REMIND_WINDOW_MIN = 30 // 会议开始前 30 分钟内提醒
|
const REMIND_STORAGE_KEY = 'meeting_remind_sent'
|
const POLL_INTERVAL = 60 * 1000 // 每 60 秒检查一次
|
|
const upcomingMeetings = ref([])
|
let remindTimer = null
|
|
const getSentMap = () => {
|
try {
|
return JSON.parse(localStorage.getItem(REMIND_STORAGE_KEY) || '{}')
|
} catch (e) {
|
return {}
|
}
|
}
|
const markSent = (key) => {
|
const m = getSentMap()
|
m[key] = Date.now()
|
// 只保留最近 200 条,避免 localStorage 无限增长
|
const entries = Object.entries(m).sort((a, b) => b[1] - a[1]).slice(0, 200)
|
localStorage.setItem(REMIND_STORAGE_KEY, JSON.stringify(Object.fromEntries(entries)))
|
}
|
|
// 计算会议开始时间(兼容 startTime 为 datetime / 纯时间两种格式)
|
const parseStart = (m) => {
|
const raw = m.startTime
|
if (!raw) return null
|
const s = String(raw)
|
if (s.length > 10) return dayjs(s)
|
if (!m.meetingDate) return null
|
return dayjs(`${m.meetingDate} ${s}`)
|
}
|
|
const checkRemind = async () => {
|
try {
|
const resp = await getMeetingPublish({ current: 1, size: 100 })
|
const records = (resp && resp.data && resp.data.records) || []
|
const now = dayjs()
|
const sent = getSentMap()
|
const upcoming = []
|
|
records.forEach((m) => {
|
const start = parseStart(m)
|
if (!start || !start.isValid()) return
|
const diffMin = start.diff(now, 'minute', true)
|
// 只提醒「即将开始」:0 ~ 30 分钟
|
if (diffMin < 0 || diffMin > REMIND_WINDOW_MIN) return
|
const flagKey = `${m.id}_${start.format('YYYYMMDDHHmm')}`
|
upcoming.push({ title: m.title || '会议', start, diffMin })
|
|
if (sent[flagKey]) return
|
markSent(flagKey)
|
ElNotification({
|
title: '会议提醒',
|
message: `【${m.title || '会议'}】将于 ${start.format('HH:mm')} 开始${
|
diffMin <= 1 ? '(即将开始)' : `(约 ${Math.round(diffMin)} 分钟后)`
|
}`,
|
type: 'warning',
|
duration: 15000,
|
position: 'bottom-right'
|
})
|
})
|
|
upcoming.sort((a, b) => a.start.valueOf() - b.start.valueOf())
|
upcomingMeetings.value = upcoming
|
} catch (e) {
|
// 提醒失败不影响主流程
|
}
|
}
|
|
const upcomingText = computed(() =>
|
upcomingMeetings.value
|
.map((x) => `${x.title}(${x.start.format('MM-DD HH:mm')})`)
|
.join(';')
|
)
|
|
onMounted(() => {
|
checkRemind()
|
remindTimer = setInterval(checkRemind, POLL_INTERVAL)
|
})
|
|
onUnmounted(() => {
|
if (remindTimer) {
|
clearInterval(remindTimer)
|
remindTimer = null
|
}
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.remind-banner {
|
margin-bottom: 12px;
|
}
|
|
.tabs-wrapper {
|
margin-bottom: 10px;
|
}
|
|
.tab-content {
|
min-height: 400px;
|
}
|
</style>
|