586 lines
16 KiB
Vue
Raw Normal View History

2026-04-22 18:04:19 +08:00
<template>
2025-06-06 15:31:45 +08:00
<commonManage>
<div class="head">
<div class="left">
<p class="desctipt">标题:</p>
<el-input v-model="data.tableNeed.title" class="m-2" placeholder="请输入文章标题" />
</div>
<div class="right">
<p v-for="(item, index) in handelData" :key="index" :class="item.className"
@click="btnClick(item.engName)">
{{ item.chinName }}</p>
</div>
</div>
<div class="cont">
2026-04-22 18:04:19 +08:00
<el-table :data="data.tableNeed.tableData" style="width: calc(100% - 40px) ;height: calc(100vh - 210px); margin:0 20px ;" fit="true"
2025-06-06 15:31:45 +08:00
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA', height: '50px' }"
:cell-style="getCellStyles" :row-style="{ height: '0px' }">
<el-table-column fixed prop="ind" label="序号" width="80" />
<el-table-column prop="title" label="文章标题" />
<el-table-column prop="abstract" label="摘要" show-overflow-tooltip />
<el-table-column prop="updateDate" label="更新时间" width="180" />
<el-table-column label="操作" width="180">
<template v-slot="{ row }">
<div class="operate">
<p class="edit" @click="viewDetail(row)">查看详情</p>
<p class="delete" @click="handleDelete(row)">删除</p>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination class="pagination" :current-page="data.tableNeed.pageIndex"
:page-size="data.tableNeed.pageSize" :page-sizes="[10, 50, 100, 200]"
layout="total, sizes, prev, pager, next, jumper" :total="data.tableNeed.total"
@size-change="handleSizeChange" @current-change="changePage" />
</div>
</commonManage>
<!-- 文章详情弹窗 -->
<el-dialog v-model="data.detailDialog.show" title="文章详情" width="50%" :close-on-click-modal="false">
<div class="article-detail">
<h2 class="article-title">{{ data.detailDialog.article.title }}</h2>
<div class="article-meta">
<span>更新时间: {{ formatDate(data.detailDialog.article.updateDate) }}</span>
</div>
<div class="article-cover" v-if="data.detailDialog.article.coverImage">
<img :src="data.detailDialog.article.coverImage" alt="封面图">
</div>
<div class="article-abstract">
<h3>摘要</h3>
<p>{{ data.detailDialog.article.abstract }}</p>
</div>
<div class="article-link" v-if="data.detailDialog.article.articleUrl">
<h3>原文链接</h3>
<a :href="data.detailDialog.article.articleUrl" target="_blank">{{ data.detailDialog.article.articleUrl }}</a>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.detailDialog.show = false">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- 新增文章弹窗 -->
<el-drawer v-model="data.addDialog.show" direction="rtl" size="50%" :close-on-click-modal="false" :show-close="false">
<template #header>
<p class="title">新增文章</p>
</template>
<template #default>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="120px" class="demo-ruleForm" size="default" status-icon>
<el-form-item label="文章标题" prop="title">
<el-input v-model="addForm.title" placeholder="请输入文章标题" />
</el-form-item>
<el-form-item label="文章摘要" prop="abstract">
<el-input v-model="addForm.abstract" type="textarea" :rows="4" placeholder="请输入文章摘要" />
</el-form-item>
<el-form-item label="原文链接" prop="articleUrl">
<el-input v-model="addForm.articleUrl" placeholder="请输入原文链接" />
</el-form-item>
<el-form-item label="封面图片" prop="coverImage" style="display: flex;align-items: center;">
<el-upload class="upload-demo" ref="uploadRef" drag :before-upload="handleBeforeUpload" :file-list="addForm.fileList">
<div class="el-upload__text">
将文件拖拽到这里或<em>点击上传</em>
</div>
</el-upload>
<div class="coverImg" v-if="coverImgUrl">
<img style="width: 100px;height: 100px;margin-left: 10px;object-fit: contain;" :src="coverImgUrl" alt="封面">
</div>
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="footer">
<p class="cancle" @click="cancelAdd">取消</p>
<p class="confirm" @click="confirmAdd">确定</p>
</div>
</template>
</el-drawer>
<!-- 删除确认弹窗 -->
<el-dialog v-model="data.deleteDialog.show" title="提示" width="30%" :close-on-click-modal="false">
<span>确定要删除该文章吗?</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.deleteDialog.show = false">取消</el-button>
<el-button type="danger" @click="confirmDelete">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import { getArticlePageList, getArticleDetails, addArticle, uploadArticleImage, deleteArticle } from '@/api/article';
import { ElMessage } from 'element-plus';
// 数据定义
const data = reactive({
tableNeed: {
title: '',
pageIndex: 1,
pageSize: 10,
total: 0,
tableData: []
},
detailDialog: {
show: false,
article: {}
},
addDialog: {
show: false
},
deleteDialog: {
show: false,
articleId: null
}
});
// 新增表单引用
const addFormRef = ref(null);
const uploadRef = ref(null);
let coverImgUrl = ref('');
// 文件上传表单
const photoForm = new FormData();
// 新增表单数据
const addForm = reactive({
title: '',
abstract: '',
articleUrl: '',
coverImage: '',
fileList: []
});
// 表单验证规则
const addRules = {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
],
articleUrl: [
{ required: true, message: '请输入原文链接', trigger: 'blur' },
],
coverImage: [
{ required: true, message: '请上传封面图片', trigger: 'change' },
]
};
// 表格样式
const getCellStyles = () => {
return {
'text-align': 'center',
'font-size': '12px',
'color': '#3F404A',
'padding': '3px',
'height': '50px'
};
};
// 操作按钮数据
const handelData = computed(() => {
return [
{
chinName: '查询',
engName: 'search',
className: 'Search'
},
{
chinName: '重置',
engName: 'reset',
className: 'reset'
},
{
chinName: '新增',
engName: 'add',
className: 'Add'
}
];
});
// 按钮点击事件
const btnClick = (type) => {
if (type === 'search') {
data.tableNeed.pageIndex = 1;
getArticleList();
} else if (type === 'reset') {
data.tableNeed.title = '';
data.tableNeed.pageIndex = 1;
getArticleList();
} else if (type === 'add') {
// 打开新增弹窗
resetAddForm();
data.addDialog.show = true;
}
};
// 重置新增表单
const resetAddForm = () => {
if (addFormRef.value) {
addFormRef.value.resetFields();
}
addForm.title = '';
addForm.abstract = '';
addForm.articleUrl = '';
addForm.coverImage = '';
addForm.fileList = [];
coverImgUrl.value = '';
};
// 图片上传前处理
const handleBeforeUpload = (file) => {
const isImage = file.type.indexOf('image') !== -1;
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
ElMessage.error('上传文件只能是图片格式!');
return false;
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!');
return false;
}
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
photoForm.append("file", file);
uploadArticleImage(photoForm).then(response => {
if (response.data.code == 200) {
const newUrl = response.data.data + '?timestamp=' + new Date().getTime();
coverImgUrl.value = newUrl;
addForm.coverImage = newUrl;
photoForm.delete("file");
} else {
ElMessage.error(response.data.message);
photoForm.delete("file");
}
}).catch(error => {
console.error(error);
photoForm.delete("file");
reject(error);
});
};
reader.onerror = function (error) {
reject(error);
};
reader.onloadend = function () {
resolve(coverImgUrl.value);
};
});
};
// 取消新增
const cancelAdd = () => {
data.addDialog.show = false;
resetAddForm();
};
// 确认新增
const confirmAdd = async () => {
if (!addFormRef.value) return;
await addFormRef.value.validate(async (valid) => {
if (valid) {
try {
const formData = {
title: addForm.title,
abstract: addForm.abstract,
articleUrl: addForm.articleUrl,
coverImage: addForm.coverImage
};
const res = await addArticle(formData);
if (res && res.data) {
ElMessage.success('新增文章成功');
data.addDialog.show = false;
resetAddForm();
getArticleList(); // 刷新列表
}
} catch (error) {
ElMessage.error('新增文章失败');
}
} else {
return false;
}
});
};
// 分页大小变化
const handleSizeChange = (val) => {
data.tableNeed.pageSize = val;
getArticleList();
};
// 页码变化
const changePage = (val) => {
data.tableNeed.pageIndex = val;
getArticleList();
};
// 查看详情
const viewDetail = async (row) => {
try {
const res = await getArticleDetails(row.id);
if (res && res.data) {
data.detailDialog.article = res.data;
data.detailDialog.show = true;
}
} catch (error) {
ElMessage.error('获取文章详情失败');
}
};
// 处理删除
const handleDelete = (row) => {
data.deleteDialog.articleId = row.id;
data.deleteDialog.show = true;
};
// 确认删除
const confirmDelete = async () => {
try {
console.log(data.deleteDialog.articleId);
const res = await deleteArticle(data.deleteDialog.articleId);
if (res && res.data && res.data.status) {
ElMessage.success('删除文章成功');
data.deleteDialog.show = false;
getArticleList(); // 刷新列表
} else {
ElMessage.error(res.data.message || '删除文章失败');
}
} catch (error) {
ElMessage.error('删除文章失败');
}
};
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return '';
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
};
// 获取文章列表
const getArticleList = async () => {
try {
const res = await getArticlePageList(
data.tableNeed.pageIndex,
data.tableNeed.pageSize,
data.tableNeed.title
);
if (res && res.data) {
data.tableNeed.tableData = res.data.datas.map((item, index) => {
return {
...item,
ind: (data.tableNeed.pageIndex - 1) * data.tableNeed.pageSize + index + 1
};
});
data.tableNeed.total = res.data.total;
}
} catch (error) {
ElMessage.error('获取文章列表失败');
}
};
// 页面加载时获取数据
onMounted(() => {
getArticleList();
});
</script>
<style lang="less" scoped>
.head {
width: 100%;
height: 72px;
display: flex;
align-items: center;
.left {
width: auto;
height: 100%;
display: flex;
align-items: center;
.desctipt {
width: 50px;
font-size: 16px;
font-family: PingFangSC;
font-weight: 500;
color: #3F404A;
margin-left: 20px;
}
.m-2 {
width: 250px;
}
}
.right {
width: 80%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
.reset {
width: 95px;
height: 40px;
background: #FAFAFA;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.15);
font-family: PingFangSC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
text-align: center;
line-height: 40px;
margin-right: 20px;
cursor: pointer;
user-select: none;
}
.Search,
.Add {
margin-right: 20px;
width: 98px;
height: 40px;
background: #4188FE;
border-radius: 4px;
text-align: center;
font-size: 14px;
font-family: PingFangSC;
font-weight: 400;
color: #FFFFFF;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.operate {
display: flex;
justify-content: center;
gap: 10px;
}
.edit {
font-size: 14px;
color: #4468BD;
cursor: pointer;
}
.delete {
font-size: 14px;
color: #F56C6C;
cursor: pointer;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
padding-right: 20px;
}
.article-detail {
padding: 20px;
}
.article-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
text-align: center;
}
.article-meta {
font-size: 14px;
color: #999;
margin-bottom: 20px;
text-align: center;
}
.article-cover {
margin-bottom: 20px;
text-align: center;
}
.article-cover img {
max-width: 100%;
max-height: 300px;
object-fit: contain;
}
.article-abstract h3,
.article-link h3 {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
}
.article-abstract p {
font-size: 14px;
line-height: 1.6;
margin-bottom: 20px;
}
.article-link a {
color: #4468BD;
text-decoration: none;
word-break: break-all;
}
.article-link a:hover {
text-decoration: underline;
}
/* 新增弹窗样式 */
.title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.footer {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
}
.cancle {
width: 80px;
height: 32px;
background: #FFFFFF;
border: 1px solid #DCDFE6;
border-radius: 4px;
font-size: 14px;
color: #3F404A;
text-align: center;
line-height: 32px;
margin-right: 10px;
cursor: pointer;
}
.confirm {
width: 80px;
height: 32px;
background: #4468BD;
border-radius: 4px;
font-size: 14px;
color: #FFFFFF;
text-align: center;
line-height: 32px;
cursor: pointer;
}
.coverImg {
display: flex;
align-items: center;
justify-content: center;
}
</style>