151 lines
3.5 KiB
Vue
151 lines
3.5 KiB
Vue
<script setup>
|
|
import { ref, onMounted } from 'vue';
|
|
import axios from 'axios';
|
|
|
|
const message = ref(null);
|
|
const visible = ref(true);
|
|
|
|
const fetchMessage = async () => {
|
|
try {
|
|
const response = await axios.get('/api/public/global-message');
|
|
if (response.data.success && response.data.data) {
|
|
message.value = response.data.data;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching global message:', error);
|
|
}
|
|
};
|
|
|
|
const dismiss = () => {
|
|
visible.value = false;
|
|
// Optional: Store in localStorage to keep it dismissed for the session
|
|
sessionStorage.setItem('dismissed_global_message', JSON.stringify(message.value));
|
|
};
|
|
|
|
onMounted(() => {
|
|
const dismissed = sessionStorage.getItem('dismissed_global_message');
|
|
fetchMessage().then(() => {
|
|
if (dismissed && message.value && JSON.stringify(message.value) === dismissed) {
|
|
visible.value = false;
|
|
}
|
|
});
|
|
|
|
// Refresh every 30 seconds
|
|
setInterval(fetchMessage, 30000);
|
|
});
|
|
|
|
const getIcon = (type) => {
|
|
switch (type) {
|
|
case 'success': return 'check-circle';
|
|
case 'warning': return 'exclamation-triangle';
|
|
case 'danger': return 'exclamation-circle';
|
|
default: return 'info-circle';
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<transition name="fade">
|
|
<div v-if="message && visible" :class="['system-broadcast', `broadcast-${message.type || 'info'}`]">
|
|
<div class="broadcast-container">
|
|
<div class="broadcast-content">
|
|
<i :class="['fas', `fa-${getIcon(message.type)}`, 'broadcast-icon']"></i>
|
|
<span class="broadcast-text">{{ message.text }}</span>
|
|
</div>
|
|
<button class="broadcast-close" @click="dismiss">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.system-broadcast {
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
position: relative;
|
|
z-index: 10001;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
animation: slideDown 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from { transform: translateY(-100%); }
|
|
to { transform: translateY(0); }
|
|
}
|
|
|
|
.broadcast-container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 15px;
|
|
}
|
|
|
|
.broadcast-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.broadcast-icon {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.broadcast-text {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.broadcast-close {
|
|
background: transparent;
|
|
border: none;
|
|
color: inherit;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
opacity: 0.7;
|
|
transition: opacity 0.2s;
|
|
padding: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.broadcast-close:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Types */
|
|
.broadcast-info {
|
|
background: linear-gradient(90deg, #0284c7, #0ea5e9);
|
|
color: white;
|
|
}
|
|
|
|
.broadcast-success {
|
|
background: linear-gradient(90deg, #059669, #10b981);
|
|
color: white;
|
|
}
|
|
|
|
.broadcast-warning {
|
|
background: linear-gradient(90deg, #d97706, #f59e0b);
|
|
color: white;
|
|
}
|
|
|
|
.broadcast-danger {
|
|
background: linear-gradient(90deg, #dc2626, #ef4444);
|
|
color: white;
|
|
}
|
|
|
|
.fade-enter-active, .fade-leave-active {
|
|
transition: opacity 0.5s, transform 0.5s;
|
|
}
|
|
|
|
.fade-enter-from, .fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
</style>
|