From 7ab50761e7b6649fc30930cfd604d663b2ea4e00 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期三, 21 十月 2026 15:16:10 +0800
Subject: [PATCH] feat(芯导): 仓储库存管理/协同审批会议提醒/采购付款查询导出/营销回款流水导出/生产设备补字段

---
 src/views/collaborativeApproval/meetingManagement/index.vue |  110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 108 insertions(+), 2 deletions(-)

diff --git a/src/views/collaborativeApproval/meetingManagement/index.vue b/src/views/collaborativeApproval/meetingManagement/index.vue
index 69b0275..1908797 100644
--- a/src/views/collaborativeApproval/meetingManagement/index.vue
+++ b/src/views/collaborativeApproval/meetingManagement/index.vue
@@ -1,5 +1,18 @@
 <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"
@@ -24,7 +37,10 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+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'
@@ -48,9 +64,100 @@
 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;
@@ -60,4 +167,3 @@
   min-height: 400px;
 }
 </style>
-

--
Gitblit v1.9.3