backend/src/views/apps/cms/articles/index.vue
2026-01-26 17:51:56 +08:00

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>