gangbi_web/frontend/src/views/admin/MessageManagement.vue

283 lines
7.5 KiB
Vue
Raw Normal View History

2025-02-07 05:10:01 +00:00
<template>
<div class="message-management">
<div class="page-header">
<h2>留言管理</h2>
</div>
<el-table :data="messages" style="width: 100%">
<el-table-column prop="name" label="姓名" width="120" />
<el-table-column prop="email" label="邮箱" width="200" />
<el-table-column prop="content" label="内容">
<template #default="scope">
<div class="message-preview">
{{ getPreviewContent(scope.row.content) }}
<el-button
v-if="scope.row.content.length > 50"
type="text"
@click="showMessageDetail(scope.row)"
>
查看更多
</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 'unread' ? 'warning' : 'success'">
{{ scope.row.status === 'unread' ? '未读' : '已读' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="created_at" label="留言时间" width="180">
<template #default="scope">
{{ formatDate(scope.row.created_at) }}
</template>
</el-table-column>
<el-table-column v-if="isAdmin" prop="read_info" label="阅读信息" width="200">
<template #default="scope">
<div v-if="scope.row.read_by">
<div>阅读者: {{ scope.row.reader_name }}</div>
<div>阅读时间: {{ formatDate(scope.row.read_at) }}</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center">
<template #default="scope">
<div class="action-buttons">
<el-button
v-if="scope.row.status === 'unread'"
size="small"
type="primary"
@click="showMessageDetail(scope.row)"
>
阅读
</el-button>
<el-button
size="small"
type="danger"
@click="deleteMessage(scope.row.id)"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 留言详情对话框 -->
<el-dialog
v-model="dialogVisible"
title="留言详情"
width="600px"
:before-close="handleDialogClose"
>
<div v-if="selectedMessage" class="message-detail">
<div class="detail-item">
<label>姓名</label>
<span>{{ selectedMessage.name }}</span>
</div>
<div class="detail-item">
<label>邮箱</label>
<span>{{ selectedMessage.email }}</span>
</div>
<div class="detail-item">
<label>留言时间</label>
<span>{{ formatDate(selectedMessage.created_at) }}</span>
</div>
<div class="detail-item">
<label>内容</label>
<div class="message-content">{{ selectedMessage.content }}</div>
</div>
<div v-if="isAdmin && selectedMessage.read_by" class="detail-item">
<label>阅读信息</label>
<div>
<p>阅读者: {{ selectedMessage.reader_name }}</p>
<p>阅读时间: {{ formatDate(selectedMessage.read_at) }}</p>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { ref, onMounted, computed, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'
import { useRouter } from 'vue-router'
export default {
name: 'MessageManagement',
setup() {
const router = useRouter()
const messages = ref([])
const dialogVisible = ref(false)
const selectedMessage = ref(null)
const currentUserRole = localStorage.getItem('admin_role')
const ws = ref(null)
const unreadCount = ref(0)
const isAdmin = computed(() => {
return ['superadmin', 'admin'].includes(currentUserRole)
})
const connectWebSocket = () => {
const token = localStorage.getItem('admin_token')
if (!token) return
const wsUrl = `ws://localhost:3000?token=${token}`
ws.value = new WebSocket(wsUrl)
ws.value.onmessage = (event) => {
const data = JSON.parse(event.data)
if (data.type === 'new_message') {
// 将新消息添加到列表开头
messages.value.unshift(data.data)
}
}
ws.value.onclose = () => {
setTimeout(connectWebSocket, 5000) // 断线重连
}
}
const fetchMessages = async () => {
try {
const response = await axios.get('/api/messages')
messages.value = response.data
unreadCount.value = messages.value.filter(m => m.status === 'unread').length
window.dispatchEvent(new CustomEvent('unread-messages-update', {
detail: unreadCount.value
}))
} catch (error) {
ElMessage.error('获取留言列表失败')
}
}
const getPreviewContent = (content) => {
return content.length > 50 ? content.slice(0, 50) + '...' : content
}
const showMessageDetail = async (message) => {
selectedMessage.value = message
dialogVisible.value = true
// 如果是未读消息,标记为已读
if (message.status === 'unread') {
try {
await axios.put(`/api/messages/${message.id}/read`)
message.status = 'read'
unreadCount.value--
window.dispatchEvent(new CustomEvent('unread-messages-update', {
detail: unreadCount.value
}))
// 重新获取消息列表以更新阅读信息
fetchMessages()
} catch (error) {
console.error('Error marking message as read:', error)
}
}
}
const deleteMessage = async (id) => {
try {
await ElMessageBox.confirm(
'确定要删除这条留言吗?此操作不可恢复',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await axios.delete(`/api/messages/${id}`)
ElMessage.success('留言删除成功')
messages.value = messages.value.filter(msg => msg.id !== id)
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
const formatDate = (date) => {
return new Date(date).toLocaleString()
}
const handleDialogClose = (done) => {
selectedMessage.value = null
done()
}
onMounted(() => {
fetchMessages()
connectWebSocket()
})
onUnmounted(() => {
if (ws.value) {
ws.value.close()
}
})
return {
messages,
dialogVisible,
selectedMessage,
isAdmin,
getPreviewContent,
showMessageDetail,
deleteMessage,
formatDate,
handleDialogClose
}
}
}
</script>
<style scoped>
.message-management {
padding: 20px;
}
.page-header {
margin-bottom: 20px;
}
.action-buttons {
display: flex;
gap: 8px;
justify-content: center;
}
.message-preview {
display: flex;
align-items: center;
gap: 10px;
}
.message-detail {
padding: 20px;
}
.detail-item {
margin-bottom: 15px;
}
.detail-item label {
font-weight: bold;
margin-right: 10px;
color: #666;
}
.message-content {
margin-top: 10px;
white-space: pre-wrap;
line-height: 1.5;
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
}
</style>