245 lines
7.2 KiB
Vue
245 lines
7.2 KiB
Vue
<template>
|
|
<a-card title="游记管理">
|
|
<template #extra>
|
|
<a-space wrap>
|
|
<a-input
|
|
v-model:value="query.keyword"
|
|
placeholder="搜索标题/标签"
|
|
allow-clear
|
|
style="width: 200px"
|
|
@press-enter="handleSearch"
|
|
/>
|
|
<a-select v-model:value="query.status" placeholder="状态" allow-clear style="width: 120px" @change="handleSearch">
|
|
<a-select-option :value="0">待审核</a-select-option>
|
|
<a-select-option :value="1">已发布</a-select-option>
|
|
<a-select-option :value="2">已下架</a-select-option>
|
|
</a-select>
|
|
<a-button type="primary" @click="handleSearch">
|
|
<SearchOutlined /> 查询
|
|
</a-button>
|
|
</a-space>
|
|
</template>
|
|
|
|
<a-table
|
|
:data-source="tableData"
|
|
:columns="columns"
|
|
:loading="loading"
|
|
:pagination="pagination"
|
|
row-key="id"
|
|
@change="handleTableChange"
|
|
/>
|
|
<a-empty v-if="!loading && tableData.length === 0" description="暂无游记数据" />
|
|
</a-card>
|
|
|
|
<a-modal v-model:open="viewVisible" title="游记详情" width="720px" :footer="null">
|
|
<div v-if="currentNote" class="note-detail">
|
|
<img v-if="currentNote.coverImage" :src="currentNote.coverImage" alt="封面" class="note-cover" />
|
|
<h3>{{ currentNote.title }}</h3>
|
|
<div class="meta">
|
|
<span>作者:{{ currentNote.userNickname || '-' }}</span>
|
|
<span>关联景点:{{ currentNote.attractionName || '-' }}</span>
|
|
<span>状态:{{ statusText(currentNote.status) }}</span>
|
|
<span>发布时间:{{ currentNote.publishTime || currentNote.createTime }}</span>
|
|
</div>
|
|
<div v-if="currentNote.tags" class="tags">
|
|
<a-tag v-for="t in currentNote.tags.split(/[;,]/)" :key="t">{{ t }}</a-tag>
|
|
</div>
|
|
<a-divider />
|
|
<div class="note-content" v-html="currentNote.content"></div>
|
|
</div>
|
|
</a-modal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, h, onMounted } from 'vue'
|
|
import { getTravelNotePage, getTravelNoteById, auditTravelNote, deleteTravelNote } from '@/api/travel'
|
|
import { message, Modal } from 'ant-design-vue'
|
|
|
|
const tableData = ref<any[]>([])
|
|
const loading = ref(false)
|
|
const viewVisible = ref(false)
|
|
const currentNote = ref<any>(null)
|
|
|
|
const query = reactive({
|
|
keyword: '',
|
|
status: undefined as number | undefined,
|
|
})
|
|
|
|
const pagination = reactive({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
showSizeChanger: true,
|
|
showTotal: (total: number) => `共 ${total} 条`,
|
|
})
|
|
|
|
const statusMap: Record<number, { text: string; color: string }> = {
|
|
0: { text: '待审核', color: 'orange' },
|
|
1: { text: '已发布', color: 'green' },
|
|
2: { text: '已下架', color: 'red' },
|
|
}
|
|
|
|
function statusText(status: number) {
|
|
return (statusMap[status] || { text: '未知' }).text
|
|
}
|
|
|
|
const columns = [
|
|
{ title: 'ID', dataIndex: 'id', width: 70 },
|
|
{ title: '标题', dataIndex: 'title', minWidth: 200, ellipsis: true },
|
|
{ title: '关联景点', dataIndex: 'attractionName', width: 120, ellipsis: true, customRender: ({ text }: any) => text || '-' },
|
|
{ title: '作者', dataIndex: 'userNickname', width: 100, customRender: ({ text }: any) => text || '-' },
|
|
{ title: '浏览量', dataIndex: 'viewCount', width: 80 },
|
|
{ title: '点赞', dataIndex: 'likeCount', width: 70 },
|
|
{ title: '评论', dataIndex: 'commentCount', width: 70 },
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 100,
|
|
customRender: ({ text, record }: any) => {
|
|
const info = statusMap[text] || { text: '未知', color: 'default' }
|
|
return h('a-tag', { color: info.color, title: record.rejectReason || '' }, info.text)
|
|
},
|
|
},
|
|
{
|
|
title: '发布时间',
|
|
dataIndex: 'publishTime',
|
|
width: 180,
|
|
customRender: ({ text, record }: any) => text || record.createTime || '',
|
|
},
|
|
{
|
|
title: '操作',
|
|
width: 240,
|
|
fixed: 'right',
|
|
customRender: ({ record }: any) => {
|
|
const btns: any[] = []
|
|
btns.push(h('a', { style: 'margin-right: 16px', onClick: () => handleView(record) }, '查看'))
|
|
if (record.status === 0) {
|
|
btns.push(h('a', { style: 'margin-right: 16px; color: #52c41a', onClick: () => handleAudit(record, 1) }, '通过'))
|
|
btns.push(h('a', { style: 'margin-right: 16px; color: #faad14', onClick: () => handleReject(record) }, '驳回'))
|
|
} else if (record.status === 1) {
|
|
btns.push(h('a', { style: 'margin-right: 16px', onClick: () => handleAudit(record, 2) }, '下架'))
|
|
} else if (record.status === 2) {
|
|
btns.push(h('a', { style: 'margin-right: 16px; color: #52c41a', onClick: () => handleAudit(record, 1) }, '重新发布'))
|
|
}
|
|
btns.push(h('a', { style: 'color: #ff4d4f', onClick: () => handleDelete(record) }, '删除'))
|
|
return h('span', null, btns)
|
|
},
|
|
},
|
|
]
|
|
|
|
async function fetchData() {
|
|
loading.value = true
|
|
try {
|
|
const res: any = await getTravelNotePage({
|
|
current: pagination.current,
|
|
size: pagination.pageSize,
|
|
keyword: query.keyword || undefined,
|
|
status: query.status,
|
|
})
|
|
tableData.value = res.data.records || []
|
|
pagination.total = res.data.total || 0
|
|
} catch (err: any) {
|
|
message.error(err.message || '获取游记列表失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function handleView(row: any) {
|
|
try {
|
|
const res: any = await getTravelNoteById(row.id)
|
|
currentNote.value = res.data
|
|
viewVisible.value = true
|
|
} catch (err: any) {
|
|
message.error(err.message || '获取游记详情失败')
|
|
}
|
|
}
|
|
|
|
function handleSearch() {
|
|
pagination.current = 1
|
|
fetchData()
|
|
}
|
|
|
|
function handleTableChange(pag: any) {
|
|
pagination.current = pag.current
|
|
pagination.pageSize = pag.pageSize
|
|
fetchData()
|
|
}
|
|
|
|
async function handleAudit(row: any, status: number) {
|
|
try {
|
|
await auditTravelNote({ id: row.id, status })
|
|
message.success(status === 1 ? '已发布' : '已下架')
|
|
fetchData()
|
|
} catch (err: any) {
|
|
message.error(err.message || '操作失败')
|
|
}
|
|
}
|
|
|
|
function handleReject(row: any) {
|
|
Modal.confirm({
|
|
title: '确认驳回该游记?',
|
|
content: '驳回后该游记将保持待审核状态并记录驳回原因。',
|
|
okText: '确认',
|
|
cancelText: '取消',
|
|
onOk: async () => {
|
|
await auditTravelNote({ id: row.id, status: 0, rejectReason: '审核未通过' })
|
|
message.success('已驳回')
|
|
fetchData()
|
|
},
|
|
})
|
|
}
|
|
|
|
function handleDelete(row: any) {
|
|
Modal.confirm({
|
|
title: `确认删除游记「${row.title}」?`,
|
|
okText: '确认',
|
|
cancelText: '取消',
|
|
okButtonProps: { danger: true },
|
|
onOk: async () => {
|
|
await deleteTravelNote(row.id)
|
|
message.success('删除成功')
|
|
fetchData()
|
|
},
|
|
})
|
|
}
|
|
|
|
onMounted(fetchData)
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.note-detail {
|
|
h3 { margin-bottom: 12px; }
|
|
|
|
.note-cover {
|
|
width: 100%;
|
|
max-height: 300px;
|
|
object-fit: cover;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.meta {
|
|
font-size: 13px;
|
|
color: #666;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
}
|
|
|
|
.tags {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.note-content {
|
|
line-height: 1.8;
|
|
word-break: break-word;
|
|
|
|
:deep(img), :deep(video) {
|
|
max-width: 100%;
|
|
border-radius: 8px;
|
|
}
|
|
}
|
|
}
|
|
</style>
|