gangbi_web/frontend/src/views/admin/MessageManagement.vue
2025-02-07 13:10:01 +08:00

283 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>