feat: 管理后台全面升级——修复菜单无数据根因(items字段不匹配+trends接口缺失),新增知识管理(审核/检测题编辑)/访问日志/看板v2(最近访问+点亮分布+全量指标),trends CRUD,用户/反馈列表修正
This commit is contained in:
+77
-27
@@ -1,43 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="head"><h2>趋势管理</h2><el-button type="primary" @click="dialog=true">新增趋势</el-button></div>
|
||||
<el-table :data="list">
|
||||
<el-table-column prop="title" label="标题" show-overflow-tooltip />
|
||||
<el-table-column prop="source" label="来源" width="100" />
|
||||
<div class="page">
|
||||
<div class="toolbar">
|
||||
<el-input v-model="q" placeholder="搜索标题" style="width: 220px" clearable @keyup.enter="search" />
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button type="success" @click="openEdit(null)">新增资讯</el-button>
|
||||
</div>
|
||||
<el-table :data="list" stripe v-loading="loading">
|
||||
<el-table-column label="标题" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.title }}
|
||||
<el-tag v-if="row.hot" type="danger" size="small" style="margin-left:4px">热</el-tag>
|
||||
<el-tag v-if="row.picked" type="warning" size="small" style="margin-left:4px">精</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="category" label="分类" width="80" />
|
||||
<el-table-column prop="hot" label="热门" width="70"><template #default="{row}"><el-tag :type="row.hot?'danger':'info'" size="small">{{row.hot?'热':'普通'}}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" width="160"><template #default="{row}">{{ row.createdAt?.slice(0,10) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="100"><template #default="{row}"><el-button size="small" type="danger" plain @click="del(row)">删除</el-button></template></el-table-column>
|
||||
<el-table-column prop="source" label="来源" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="日期" width="110">
|
||||
<template #default="{ row }">{{ (row.newsDate || row.createdAt || '').slice(0, 10) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="del(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-dialog v-model="dialog" title="新增趋势" width="480">
|
||||
<el-form label-width="70">
|
||||
<el-pagination background layout="total, prev, pager, next" :total="total" :page-size="pageSize" v-model:current-page="page" @current-change="load" style="margin-top: 12px" />
|
||||
|
||||
<el-dialog v-model="dlg" :title="form._id ? '编辑资讯' : '新增资讯'" width="560px">
|
||||
<el-form label-width="76px">
|
||||
<el-form-item label="标题"><el-input v-model="form.title" /></el-form-item>
|
||||
<el-form-item label="来源"><el-input v-model="form.source" placeholder="来源名称" /></el-form-item>
|
||||
<el-form-item label="分类"><el-select v-model="form.category"><el-option v-for="c in ['产品','论文','工具','公司','政策','其他']" :key="c" :value="c" :label="c" /></el-select></el-form-item>
|
||||
<el-form-item label="摘要"><el-input v-model="form.summary" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item label="摘要"><el-input v-model="form.summary" type="textarea" :rows="2" /></el-form-item>
|
||||
<el-form-item label="来源/链接">
|
||||
<el-input v-model="form.source" style="width: 140px" />
|
||||
<el-input v-model="form.sourceUrl" placeholder="原文链接(选填)" style="flex:1; margin-left:8px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-select v-model="form.category" style="width: 130px"><el-option v-for="c in ['产品','论文','工具','公司','政策','其他']" :key="c" :label="c" :value="c" /></el-select>
|
||||
<el-switch v-model="form.hot" active-text="热门" style="margin-left: 16px" />
|
||||
<el-switch v-model="form.picked" active-text="精选" style="margin-left: 12px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialog=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template>
|
||||
<template #footer>
|
||||
<el-button @click="dlg = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
|
||||
const list = ref([]); const dialog = ref(false)
|
||||
const form = reactive({ title: '', source: '', category: '其他', summary: '' })
|
||||
async function load() { /* TODO */ }
|
||||
import { apiGet, apiPost, apiPut, apiDelete } from '../api'
|
||||
const list = ref([]), total = ref(0), page = ref(1), pageSize = 20, q = ref(''), loading = ref(false)
|
||||
const dlg = ref(false)
|
||||
const form = ref({})
|
||||
function search() { page.value = 1; load() }
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const r = await apiGet(`/api/admin/trends?page=${page.value}&limit=${pageSize}&keyword=${encodeURIComponent(q.value)}`)
|
||||
list.value = r.items || []
|
||||
total.value = r.pagination?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
function openEdit(row) {
|
||||
form.value = row ? { ...row } : { title: '', summary: '', source: '', sourceUrl: '', category: '产品', hot: false, picked: false }
|
||||
dlg.value = true
|
||||
}
|
||||
async function save() {
|
||||
try { await apiPost('/api/admin/trends', { ...form }); ElMessage.success('已保存'); dialog.value = false; load() }
|
||||
catch (e) { ElMessage.error(e.message) }
|
||||
if (!form.value.title) return ElMessage.warning('标题必填')
|
||||
try {
|
||||
if (form.value._id) await apiPut(`/api/admin/trends/${form.value._id}`, form.value)
|
||||
else await apiPost('/api/admin/trends', form.value)
|
||||
ElMessage.success('已保存')
|
||||
dlg.value = false
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e.message || '保存失败') }
|
||||
}
|
||||
async function del(row) {
|
||||
await ElMessageBox.confirm('确认删除?'); await apiDelete(`/api/admin/trends/${row._id}`); ElMessage.success('已删除'); load()
|
||||
await ElMessageBox.confirm(`删除「${row.title}」?`, '确认', { type: 'warning' })
|
||||
try { await apiDelete(`/api/admin/trends/${row._id}`); ElMessage.success('已删除'); load() }
|
||||
catch (e) { ElMessage.error(e.message || '删除失败') }
|
||||
}
|
||||
function apiDelete(url) { return fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${localStorage.getItem('dim_admin_token')}` } }) }
|
||||
function apiPost(url, body) { return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('dim_admin_token')}` }, body: JSON.stringify(body) }).then(r => r.json()) }
|
||||
function apiGet(path) { return fetch(path, { headers: { Authorization: `Bearer ${localStorage.getItem('dim_admin_token')}` } }).then(r => r.json()) }
|
||||
onMounted(load)
|
||||
</script>
|
||||
<style scoped>.head{display:flex;justify-content:space-between;align-items:center}</style>
|
||||
|
||||
<style scoped>
|
||||
.page { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,.06); }
|
||||
.toolbar { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user