Files
schoolNews/schoolNewsWeb/src/views/article/ArticleShowView.vue

340 lines
7.4 KiB
Vue
Raw Normal View History

2025-10-20 11:25:34 +08:00
<template>
<!-- Dialog 模式 -->
<el-dialog
v-if="asDialog"
v-model="visible"
:title="title"
:width="width"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="article-show-container">
<!-- 文章头部信息 -->
<div class="article-header">
<h1 class="article-title">{{ articleData.title }}</h1>
2025-10-20 18:28:38 +08:00
<div class="article-meta-info">
<div class="meta-item" v-if="articleData.publishTime || articleData.createTime">
发布时间{{ formatDateSimple(articleData.publishTime || articleData.createTime || '') }}
</div>
<div class="meta-item" v-if="articleData.viewCount !== undefined">
浏览次数{{ articleData.viewCount }}
</div>
<div class="meta-item" v-if="articleData.source">
来源{{ articleData.source }}
</div>
2025-10-20 11:25:34 +08:00
</div>
</div>
2025-10-20 18:28:38 +08:00
<!-- 分隔线 -->
<div class="separator"></div>
2025-10-20 11:25:34 +08:00
<!-- 文章内容 -->
<div class="article-content ql-editor" v-html="articleData.content"></div>
</div>
<template #footer>
<el-button @click="handleClose">关闭</el-button>
<el-button v-if="showEditButton" type="primary" @click="handleEdit">编辑</el-button>
</template>
</el-dialog>
<!-- Dialog 模式 -->
<div v-else class="article-show-container">
<!-- 文章头部信息 -->
<div class="article-header">
<h1 class="article-title">{{ articleData.title }}</h1>
2025-10-20 18:28:38 +08:00
<div class="article-meta-info">
<div class="meta-item" v-if="articleData.publishTime || articleData.createTime">
发布时间{{ formatDateSimple(articleData.publishTime || articleData.createTime || '') }}
</div>
<div class="meta-item" v-if="articleData.viewCount !== undefined">
浏览次数{{ articleData.viewCount }}
</div>
<div class="meta-item" v-if="articleData.source">
来源{{ articleData.source }}
</div>
2025-10-20 11:25:34 +08:00
</div>
</div>
2025-10-20 18:28:38 +08:00
<!-- 分隔线 -->
<div class="separator"></div>
2025-10-20 11:25:34 +08:00
<!-- 文章内容 -->
<div class="article-content ql-editor" v-html="articleData.content"></div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { ElDialog, ElButton } from 'element-plus';
2025-10-20 18:28:38 +08:00
import { ResourceCategory, Resource, Tag } from '@/types';
2025-10-20 11:25:34 +08:00
interface Props {
modelValue?: boolean; // Dialog 模式下的显示状态
asDialog?: boolean; // 是否作为 Dialog 使用
title?: string; // Dialog 标题
width?: string; // Dialog 宽度
2025-10-20 18:28:38 +08:00
articleData?: Resource; // 文章数据
2025-10-20 15:08:41 +08:00
categoryList?: Array<ResourceCategory>; // 分类列表
2025-10-20 11:25:34 +08:00
showEditButton?: boolean; // 是否显示编辑按钮
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
asDialog: true,
title: '文章预览',
width: '900px',
articleData: () => ({}),
categoryList: () => [],
showEditButton: false
});
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'close': [];
'edit': [];
}>();
// Dialog 显示状态
const visible = computed({
get: () => props.asDialog ? props.modelValue : false,
set: (val) => props.asDialog ? emit('update:modelValue', val) : undefined
});
2025-10-20 18:28:38 +08:00
// 格式化日期简单格式YYYY-MM-DD
function formatDateSimple(date: string | Date): string {
2025-10-20 11:25:34 +08:00
if (!date) return '';
const d = new Date(date);
2025-10-20 18:28:38 +08:00
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
2025-10-20 11:25:34 +08:00
}
// 关闭处理
function handleClose() {
if (props.asDialog) {
visible.value = false;
}
emit('close');
}
// 编辑处理
function handleEdit() {
emit('edit');
}
// 暴露方法
defineExpose({
open: () => {
if (props.asDialog) {
visible.value = true;
}
},
close: handleClose
});
</script>
<style lang="scss" scoped>
.article-show-container {
max-width: 100%;
margin: 0 auto;
background: #fff;
}
.article-header {
2025-10-20 18:28:38 +08:00
margin-bottom: 36px;
2025-10-20 11:25:34 +08:00
}
.article-title {
2025-10-20 18:28:38 +08:00
font-family: 'PingFang SC';
font-weight: 600;
2025-10-20 11:25:34 +08:00
font-size: 28px;
2025-10-20 18:28:38 +08:00
line-height: 28px;
color: #141F38;
margin: 0 0 30px 0;
text-align: center;
2025-10-20 11:25:34 +08:00
}
2025-10-20 18:28:38 +08:00
.article-meta-info {
2025-10-20 11:25:34 +08:00
display: flex;
2025-10-20 18:28:38 +08:00
align-items: center;
gap: 48px;
margin-top: 30px;
justify-content: center;
2025-10-20 11:25:34 +08:00
}
.meta-item {
2025-10-20 18:28:38 +08:00
font-family: 'PingFang SC';
font-weight: 400;
font-size: 14px;
line-height: 28px;
color: #979797;
}
.separator {
width: 100%;
height: 1px;
background: #E9E9E9;
margin-bottom: 40px;
2025-10-20 11:25:34 +08:00
}
.article-cover {
margin-bottom: 24px;
text-align: center;
}
.cover-image {
max-width: 100%;
max-height: 400px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.article-content {
2025-10-20 18:28:38 +08:00
font-family: 'PingFang SC';
font-weight: 400;
2025-10-20 11:25:34 +08:00
font-size: 16px;
2025-10-20 18:28:38 +08:00
line-height: 30px;
color: #334155;
2025-10-20 11:25:34 +08:00
// 继承富文本编辑器的样式
:deep(img) {
max-width: 100%;
height: auto;
display: inline-block;
vertical-align: bottom;
}
:deep(video),
:deep(iframe) {
max-width: 100%;
height: auto;
display: inline-block;
vertical-align: bottom;
}
// 对齐方式样式 - 图片和视频分别处理
:deep(.ql-align-center) {
text-align: center !important;
// 视频始终居中显示
video, .custom-video {
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
}
// 图片跟随文字对齐
img, .custom-image {
display: inline-block !important;
vertical-align: bottom !important;
}
}
:deep(.ql-align-right) {
text-align: right !important;
// 视频始终居中显示
video, .custom-video {
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
}
// 图片跟随文字对齐
img, .custom-image {
display: inline-block !important;
vertical-align: bottom !important;
}
}
:deep(.ql-align-left) {
text-align: left !important;
// 视频始终居中显示
video, .custom-video {
display: block !important;
margin-left: auto !important;
margin-right: auto !important;
}
// 图片跟随文字对齐
img, .custom-image {
display: inline-block !important;
vertical-align: bottom !important;
}
}
// 其他富文本样式
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
margin: 24px 0 16px 0;
font-weight: bold;
color: #303133;
}
:deep(p) {
margin: 16px 0;
}
:deep(blockquote) {
margin: 16px 0;
padding: 16px;
background: #f5f7fa;
border-left: 4px solid #409eff;
border-radius: 4px;
}
:deep(code) {
background: #f5f7fa;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 14px;
}
:deep(pre) {
background: #f5f7fa;
padding: 16px;
border-radius: 4px;
overflow-x: auto;
margin: 16px 0;
code {
background: none;
padding: 0;
}
}
:deep(ul), :deep(ol) {
margin: 16px 0;
padding-left: 24px;
}
:deep(li) {
margin: 8px 0;
}
:deep(table) {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
}
:deep(th), :deep(td) {
border: 1px solid #ebeef5;
padding: 8px 12px;
text-align: left;
}
:deep(th) {
background: #f5f7fa;
font-weight: bold;
}
}
</style>