669 lines
17 KiB
Vue
669 lines
17 KiB
Vue
<template>
|
|
<div class="cms-articles">
|
|
<div class="articles-container">
|
|
<!-- 顶部操作栏 -->
|
|
<div class="toolbar">
|
|
<el-button type="primary" @click="handleAdd">新增文章</el-button>
|
|
<el-button @click="handleRefresh">刷新</el-button>
|
|
<div class="search-bar">
|
|
<el-input
|
|
v-model="searchQuery"
|
|
placeholder="搜索文章标题/作者"
|
|
clearable
|
|
@clear="handleSearch"
|
|
>
|
|
<template #append>
|
|
<el-button :icon="Search" @click="handleSearch" />
|
|
</template>
|
|
</el-input>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 筛选条件 -->
|
|
<div class="filters">
|
|
<!-- 根据分类筛选 -->
|
|
<el-select
|
|
v-model="categoryFilter"
|
|
placeholder="选择分类"
|
|
clearable
|
|
@change="handleFilterChange"
|
|
style="width: 150px; margin-right: 10px"
|
|
>
|
|
<el-option
|
|
v-for="item in categoryOptions"
|
|
:key="item.id"
|
|
:label="item.name"
|
|
:value="item.id"
|
|
/>
|
|
</el-select>
|
|
|
|
<!-- 根据日期筛选 -->
|
|
<el-date-picker
|
|
v-model="dateFilter"
|
|
type="daterange"
|
|
value-format="yyyy-MM-dd"
|
|
range-separator="至"
|
|
start-placeholder="开始日期"
|
|
end-placeholder="结束日期"
|
|
style="width: 240px; margin-right: 10px"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 文章列表 -->
|
|
<el-table
|
|
:data="articleList"
|
|
v-loading="loading"
|
|
stripe
|
|
border
|
|
style="width: 100%"
|
|
>
|
|
<el-table-column
|
|
prop="title"
|
|
label="标题"
|
|
min-width="400"
|
|
show-overflow-tooltip
|
|
>
|
|
<template #default="{ row }">
|
|
<div style="display: flex; align-items: center; gap: 4px">
|
|
<!-- 置顶标签 -->
|
|
<el-tag
|
|
v-if="row.top === 1"
|
|
type="danger"
|
|
size="small"
|
|
effect="dark"
|
|
>
|
|
置顶
|
|
</el-tag>
|
|
<!-- 推荐标签 -->
|
|
<el-tag
|
|
v-if="row.recommend === 1"
|
|
type="warning"
|
|
size="small"
|
|
effect="dark"
|
|
>
|
|
推荐
|
|
</el-tag>
|
|
<!-- 标题链接 -->
|
|
<el-link
|
|
type="primary"
|
|
@click="handleView(row)"
|
|
underline="never"
|
|
>
|
|
{{ row.title }}
|
|
</el-link>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="cate" label="文章分类" width="120" />
|
|
<el-table-column prop="author" label="作者" width="120" />
|
|
<el-table-column prop="status" label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="getStatusType(row.status)" size="small">
|
|
{{ getStatusText(row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
prop="views"
|
|
label="浏览量"
|
|
width="100"
|
|
align="center"
|
|
/>
|
|
<el-table-column
|
|
prop="likes"
|
|
label="点赞量"
|
|
width="100"
|
|
align="center"
|
|
/>
|
|
<el-table-column prop="publish_date" label="发布时间" width="160" />
|
|
<el-table-column prop="update_time" label="更新时间" width="160" />
|
|
<el-table-column prop="publisher" label="发布人" width="120" />
|
|
<el-table-column label="操作" width="260" fixed="right" align="center">
|
|
<template #default="{ row }">
|
|
<el-button
|
|
v-if="row.status != 2 && row.status != 0"
|
|
size="small"
|
|
type=""
|
|
@click="handlePulish(row)"
|
|
>发布</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status != 2"
|
|
size="small"
|
|
type="primary"
|
|
@click="handleEdit(row)"
|
|
>编辑</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status != 2"
|
|
size="small"
|
|
type="danger"
|
|
@click="handleDelete(row)"
|
|
>删除</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status === 2"
|
|
size="small"
|
|
type="danger"
|
|
@click="handleUnPulish(row)"
|
|
>下架</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status === 2 && row.recommend === 0"
|
|
size="small"
|
|
type=""
|
|
@click="handleRecommend(row)"
|
|
>推荐</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status === 2 && row.recommend === 1"
|
|
size="small"
|
|
type="danger"
|
|
@click="handleUnRecommend(row)"
|
|
>取消推荐</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status === 2 && row.top === 0"
|
|
size="small"
|
|
type=""
|
|
@click="handleTop(row)"
|
|
>置顶</el-button
|
|
>
|
|
<el-button
|
|
v-if="row.status === 2 && row.top === 1"
|
|
size="small"
|
|
type="danger"
|
|
@click="handleUnTop(row)"
|
|
>取消置顶</el-button
|
|
>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<div class="pagination">
|
|
<el-pagination
|
|
v-model:current-page="currentPage"
|
|
v-model:page-size="pageSize"
|
|
:total="total"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 编辑弹窗 -->
|
|
<Edit
|
|
v-model="dialogVisible"
|
|
:is-edit="isEdit"
|
|
:model="currentRow"
|
|
@saved="onSaved"
|
|
/>
|
|
|
|
<!-- 预览抽屉 -->
|
|
<Preview v-model="previewVisible" :model="currentRow" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from "vue";
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
import { Search } from "@element-plus/icons-vue";
|
|
import Edit from "./components/edit.vue";
|
|
import Preview from "./components/preview.vue";
|
|
import { useAuthStore } from "@/stores/auth";
|
|
import {
|
|
listArticles,
|
|
deleteArticle,
|
|
listCategories,
|
|
publishArticle,
|
|
unPublishArticle,
|
|
getArticle,
|
|
articleRecommend,
|
|
articleTop,
|
|
unArticleRecommend,
|
|
unArticleTop,
|
|
} from "@/api/article.js";
|
|
|
|
const loading = ref(false);
|
|
const articleList = ref([]);
|
|
const searchQuery = ref("");
|
|
const categoryFilter = ref("");
|
|
const categoryOptions = ref([]);
|
|
const currentPage = ref(1);
|
|
const pageSize = ref(10);
|
|
const total = ref(0);
|
|
const dialogVisible = ref(false);
|
|
const previewVisible = ref(false);
|
|
const isEdit = ref(false);
|
|
const currentRow = ref(null);
|
|
|
|
// 使用 auth store 获取用户信息
|
|
const authStore = useAuthStore();
|
|
|
|
// 获取用户信息
|
|
const userInfo = authStore.user;
|
|
if (userInfo && userInfo.id) {
|
|
// console.log('用户名:', userInfo.account || userInfo.name);
|
|
// console.log('用户ID:', userInfo.id);
|
|
// console.log('角色:', userInfo.role);
|
|
} else {
|
|
console.log("未找到用户信息或用户未登录");
|
|
}
|
|
|
|
// 获取状态文本
|
|
function getStatusText(status) {
|
|
const statusMap = {
|
|
0: "草稿",
|
|
1: "待审核",
|
|
2: "已发布",
|
|
3: "已隐藏",
|
|
};
|
|
return statusMap[status] || "未知";
|
|
}
|
|
|
|
// 获取状态对应的标签类型
|
|
function getStatusType(status) {
|
|
const typeMap = {
|
|
0: "info",
|
|
1: "warning",
|
|
2: "success",
|
|
3: "danger",
|
|
};
|
|
return typeMap[status] || "info";
|
|
}
|
|
|
|
// 获取推荐文本
|
|
function getRecommendText(status) {
|
|
const statusMap = {
|
|
0: "未推荐",
|
|
1: "推荐",
|
|
};
|
|
return statusMap[status] || "未知";
|
|
}
|
|
|
|
// 获取推荐对应的标签类型
|
|
function getRecommendType(status) {
|
|
const typeMap = {
|
|
0: "info",
|
|
1: "success",
|
|
};
|
|
return typeMap[status] || "info";
|
|
}
|
|
|
|
// 获取文章列表
|
|
async function fetchArticleList() {
|
|
loading.value = true;
|
|
try {
|
|
const params = {
|
|
keyword: searchQuery.value,
|
|
cate: categoryFilter.value,
|
|
page: currentPage.value,
|
|
pageSize: pageSize.value,
|
|
};
|
|
|
|
const res = await listArticles(params);
|
|
|
|
if (res.code === 200) {
|
|
articleList.value = res.data.list || [];
|
|
total.value = res.data.total || 0;
|
|
} else {
|
|
console.error("获取文章列表失败:", res.msg);
|
|
ElMessage.error(res.msg || "获取文章列表失败");
|
|
}
|
|
} catch (error) {
|
|
console.error("请求异常:", error);
|
|
ElMessage.error("网络请求失败,请稍后重试");
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
// 处理筛选变化
|
|
function handleFilterChange() {
|
|
currentPage.value = 1;
|
|
fetchArticleList();
|
|
}
|
|
|
|
// 处理每页条数变化
|
|
function handleSizeChange(val) {
|
|
pageSize.value = val;
|
|
fetchArticleList();
|
|
}
|
|
|
|
// 处理当前页变化
|
|
function handleCurrentChange(val) {
|
|
fetchArticleList();
|
|
}
|
|
|
|
function handleAdd() {
|
|
isEdit.value = false;
|
|
currentRow.value = null;
|
|
dialogVisible.value = true;
|
|
}
|
|
|
|
function handleEdit(row) {
|
|
isEdit.value = true;
|
|
// 获取详情
|
|
getArticle(row.id).then((res) => {
|
|
const resp =
|
|
res && typeof res.code !== "undefined"
|
|
? res
|
|
: res && res.data
|
|
? res.data
|
|
: res;
|
|
if (resp && resp.code === 200 && resp.data) {
|
|
const m = resp.data;
|
|
currentRow.value = {
|
|
id: m.id,
|
|
title: m.title || "",
|
|
author: m.author || "",
|
|
cate: m.cate || "",
|
|
content: m.content || "",
|
|
desc: m.desc || "",
|
|
publish_time: m.publish_time || null,
|
|
_raw: m,
|
|
};
|
|
} else {
|
|
currentRow.value = { ...row };
|
|
}
|
|
dialogVisible.value = true;
|
|
});
|
|
}
|
|
|
|
function handleView(row) {
|
|
// 获取最新详情再预览
|
|
getArticle(row.id).then((res) => {
|
|
const resp =
|
|
res && typeof res.code !== "undefined"
|
|
? res
|
|
: res && res.data
|
|
? res.data
|
|
: res;
|
|
if (resp && resp.code === 200 && resp.data) {
|
|
const m = resp.data;
|
|
currentRow.value = {
|
|
id: m.id,
|
|
title: m.title || "",
|
|
author: m.author || "",
|
|
cate: m.cate || "",
|
|
content: m.content || "",
|
|
desc: m.desc || "",
|
|
view_count: m.view_count || 0,
|
|
publisher: m.publisher || "",
|
|
create_time: m.create_time || null,
|
|
publish_time: m.publish_time || null,
|
|
update_time: m.update_time || null,
|
|
_raw: m,
|
|
};
|
|
previewVisible.value = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleDelete(row) {
|
|
ElMessageBox.confirm("确定要删除这篇文章吗?此操作不可恢复。", "提示", {
|
|
confirmButtonText: "确定",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
}).then(() => {
|
|
deleteArticle(row.id).then((res) => {
|
|
const resp =
|
|
res && typeof res.code !== "undefined"
|
|
? res
|
|
: res && res.data
|
|
? res.data
|
|
: res;
|
|
if (resp && resp.code === 200) {
|
|
ElMessage.success("删除成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error((resp && resp.msg) || "删除失败");
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
const uid = userInfo.id;
|
|
|
|
//发布文章
|
|
function handlePulish(row) {
|
|
ElMessageBox.confirm("确认发布该文章吗?发布后将在前台显示。", "确认发布", {
|
|
confirmButtonText: "确认发布",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
publishArticle(row.id, uid)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("发布成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "发布失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("发布失败:", error);
|
|
ElMessage.error(error.msg || "发布失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
// 下架文章
|
|
function handleUnPulish(row) {
|
|
ElMessageBox.confirm("确认下架该文章吗?", "确认", {
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
unPublishArticle(row.id)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("下架成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "下架失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("下架失败:", error);
|
|
ElMessage.error(error.msg || "下架失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
//推荐文章
|
|
function handleRecommend(row) {
|
|
ElMessageBox.confirm("确认推荐该文章吗?推荐后将在前台显示。", "确认推荐", {
|
|
confirmButtonText: "确认推荐",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
articleRecommend(row.id)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("推荐成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "推荐失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("推荐失败:", error);
|
|
ElMessage.error(error.msg || "推荐失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
// 取消推荐文章
|
|
function handleUnRecommend(row) {
|
|
ElMessageBox.confirm("确认取消推荐该文章吗?", "确认", {
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
unArticleRecommend(row.id)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("取消推荐成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "取消推荐失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("取消推荐失败:", error);
|
|
ElMessage.error(error.msg || "取消推荐失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
//置顶文章
|
|
function handleTop(row) {
|
|
ElMessageBox.confirm("确认置顶该文章吗?置顶后将在前台显示。", "确认置顶", {
|
|
confirmButtonText: "确认置顶",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
articleTop(row.id)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("置顶成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "置顶失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("置顶失败:", error);
|
|
ElMessage.error(error.msg || "置顶失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
// 取消置顶文章
|
|
function handleUnTop(row) {
|
|
ElMessageBox.confirm("确认取消置顶该文章吗?", "确认", {
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
})
|
|
.then(() => {
|
|
unArticleTop(row.id)
|
|
.then((res) => {
|
|
if (res.code === 200) {
|
|
ElMessage.success("取消置顶成功");
|
|
fetchArticleList();
|
|
} else {
|
|
ElMessage.error(res.msg || "取消置顶失败");
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("取消置顶失败:", error);
|
|
ElMessage.error(error.msg || "取消置顶失败");
|
|
});
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
function handleSearch() {
|
|
currentPage.value = 1;
|
|
fetchArticleList();
|
|
}
|
|
|
|
function handleRefresh() {
|
|
searchQuery.value = "";
|
|
categoryFilter.value = "";
|
|
currentPage.value = 1;
|
|
fetchArticleList();
|
|
}
|
|
|
|
// 处理保存成功回调
|
|
function onSaved() {
|
|
dialogVisible.value = false;
|
|
fetchArticleList();
|
|
ElMessage.success("保存成功");
|
|
}
|
|
|
|
// 获取分类列表
|
|
const fetchCategories = async () => {
|
|
try {
|
|
const res = await listCategories({ page: 1, limit: 1000 });
|
|
|
|
if (res && res.code === 200) {
|
|
if (res.data && Array.isArray(res.data.list)) {
|
|
categoryOptions.value = res.data.list;
|
|
} else if (Array.isArray(res.data)) {
|
|
categoryOptions.value = res.data;
|
|
} else if (Array.isArray(res.list)) {
|
|
categoryOptions.value = res.list;
|
|
}
|
|
|
|
if (categoryOptions.value.length === 0) {
|
|
ElMessage.warning("未获取到分类数据");
|
|
}
|
|
} else {
|
|
ElMessage.error(res?.msg || "获取分类列表失败");
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error("获取分类列表失败");
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await Promise.all([
|
|
fetchArticleList(),
|
|
fetchCategories(), // 获取分类数据
|
|
]);
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.cms-articles {
|
|
padding: 20px;
|
|
height: 100%;
|
|
background: var(--el-bg-color);
|
|
|
|
.articles-container {
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
|
|
.search-bar {
|
|
margin-left: auto;
|
|
width: 300px;
|
|
}
|
|
}
|
|
|
|
.filters {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.pagination {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
}
|
|
</style>
|