2026-04-22 18:04:19 +08:00

586 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<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">
<el-table :data="data.tableNeed.tableData" style="width: calc(100% - 40px) ;height: calc(100vh - 210px); margin:0 20px ;" fit="true"
: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>