|
|
@@ -0,0 +1,200 @@
|
|
|
+<template>
|
|
|
+ <div class="post-manage">
|
|
|
+ <el-card>
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>帖子管理</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-form :model="searchForm" inline>
|
|
|
+ <el-form-item label="标题">
|
|
|
+ <el-input v-model="searchForm.keyword" placeholder="搜索帖子标题" clearable @keyup.enter="handleSearch" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-select v-model="searchForm.status" clearable placeholder="全部">
|
|
|
+ <el-option label="全部" value="all" />
|
|
|
+ <el-option label="在售" value="on_sale" />
|
|
|
+ <el-option label="公开" value="public" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <el-table :data="tableData" v-loading="loading" stripe style="width: 100%">
|
|
|
+ <el-table-column prop="id" label="ID" width="70" />
|
|
|
+ <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="expertName" label="专家" width="120" />
|
|
|
+ <el-table-column label="价格" width="80">
|
|
|
+ <template #default="{ row }">¥{{ row.price }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="命中状态" width="110">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag v-if="row.hitStatus === 'hit'" type="success" size="small">命中</el-tag>
|
|
|
+ <el-tag v-else-if="row.hitStatus === 'miss'" type="info" size="small">未命中</el-tag>
|
|
|
+ <el-tag v-else type="warning" size="small">待确认</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="查看人数" width="90">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number v-model="row._viewCount" :min="0" size="small" controls-position="right"
|
|
|
+ @change="saveViewCount(row)" style="width: 100px" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="publishTime" label="发布时间" width="160" />
|
|
|
+ <el-table-column prop="expireTime" label="过期时间" width="160" />
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button size="small" @click="editHitStatus(row)">设命中</el-button>
|
|
|
+ <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="pagination-wrap">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.pageNum"
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, prev, pager, next"
|
|
|
+ @change="fetchData"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-dialog v-model="hitDialog.visible" title="设置命中状态" width="400px">
|
|
|
+ <el-radio-group v-model="hitDialog.value">
|
|
|
+ <el-radio value="pending">待确认</el-radio>
|
|
|
+ <el-radio value="hit">命中</el-radio>
|
|
|
+ <el-radio value="miss">未命中</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="hitDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="hitDialog.loading" @click="confirmHitStatus">确认</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, onMounted } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { getPostController } from '../api/post-controller'
|
|
|
+import type { PostVo, ListPostsParams } from '../api/models'
|
|
|
+
|
|
|
+const api = getPostController()
|
|
|
+const tableData = ref<(PostVo & { _viewCount?: number })[]>([])
|
|
|
+const loading = ref(false)
|
|
|
+
|
|
|
+const searchForm = reactive({ keyword: '', status: 'all' })
|
|
|
+
|
|
|
+const pagination = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
|
|
+
|
|
|
+const hitDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ postId: 0,
|
|
|
+ value: 'pending',
|
|
|
+ loading: false,
|
|
|
+})
|
|
|
+
|
|
|
+async function fetchData() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const params: ListPostsParams = {
|
|
|
+ keyword: searchForm.keyword,
|
|
|
+ pageNum: pagination.pageNum,
|
|
|
+ pageSize: pagination.pageSize,
|
|
|
+ }
|
|
|
+ if (searchForm.status && searchForm.status !== 'all') {
|
|
|
+ params.status = searchForm.status
|
|
|
+ }
|
|
|
+ const res = await api.listPosts(params)
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ tableData.value = (res.data.data ?? []).map((p) => ({ ...p, _viewCount: p.viewCount ?? 0 }))
|
|
|
+ pagination.total = res.data.total ?? 0
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ tableData.value = []
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleSearch() {
|
|
|
+ pagination.pageNum = 1
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+function handleReset() {
|
|
|
+ searchForm.keyword = ''
|
|
|
+ searchForm.status = 'all'
|
|
|
+ handleSearch()
|
|
|
+}
|
|
|
+
|
|
|
+async function saveViewCount(row: PostVo & { _viewCount?: number }) {
|
|
|
+ try {
|
|
|
+ await api.updateViewCount(Number(row.id), { viewCount: row._viewCount ?? 0 })
|
|
|
+ } catch {
|
|
|
+ ElMessage.error('更新查看人数失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function editHitStatus(row: PostVo) {
|
|
|
+ hitDialog.postId = Number(row.id)
|
|
|
+ hitDialog.value = row.hitStatus ?? 'pending'
|
|
|
+ hitDialog.visible = true
|
|
|
+}
|
|
|
+
|
|
|
+async function confirmHitStatus() {
|
|
|
+ hitDialog.loading = true
|
|
|
+ try {
|
|
|
+ await api.updateHitStatus(hitDialog.postId, { hitStatus: hitDialog.value })
|
|
|
+ ElMessage.success('命中状态已更新')
|
|
|
+ hitDialog.visible = false
|
|
|
+ await fetchData()
|
|
|
+ } catch {
|
|
|
+ ElMessage.error('更新失败')
|
|
|
+ } finally {
|
|
|
+ hitDialog.loading = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleDelete(row: PostVo) {
|
|
|
+ ElMessageBox.confirm(`确定删除帖子"${row.title}"?`, '确认删除', {
|
|
|
+ confirmButtonText: '删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async () => {
|
|
|
+ try {
|
|
|
+ await api.deletePost(Number(row.id))
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ await fetchData()
|
|
|
+ } catch {
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => fetchData())
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.post-manage {
|
|
|
+ padding: 16px;
|
|
|
+}
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.pagination-wrap {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+</style>
|