586 lines
16 KiB
Vue
586 lines
16 KiB
Vue
<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" append-to-body>
|
||
<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" append-to-body>
|
||
<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" append-to-body>
|
||
<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: 8px;
|
||
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: 8px;
|
||
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: 8px;
|
||
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: 8px;
|
||
font-size: 14px;
|
||
color: #FFFFFF;
|
||
text-align: center;
|
||
line-height: 32px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.coverImg {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
</style> |