增加网站管理功能

This commit is contained in:
2026-09-17 23:00:06 +08:00
parent 1224735aa8
commit 67e819c629
98 changed files with 8711 additions and 2254 deletions
@@ -0,0 +1,140 @@
<template>
<el-drawer v-model="visible" title="文章预览" size="60%">
<div class="preview" v-loading="!model">
<h1 class="preview__title">{{ model?.title || '-' }}</h1>
<div class="preview__meta">
<span v-if="model?.cate">分类:{{ model.cate }}</span>
<span v-if="model?.author">作者:{{ model.author }}</span>
<span>发布时间:{{ formatDateTime(model?.publish_time || model?.publish_date) }}</span>
<span v-if="model?.views">浏览:{{ model.views }}</span>
</div>
<el-divider />
<p v-if="model?.desc" class="preview__desc">{{ model.desc }}</p>
<img v-if="model?.image" :src="previewUrl(model.image)" class="preview__cover" alt="封面" />
<div v-if="model?.is_trans === 1 && model?.transurl" class="preview__trans">
本文为转载内容,原文地址:
<el-link type="primary" :href="model.transurl" target="_blank">{{ model.transurl }}</el-link>
</div>
<div class="preview__content" v-html="contentHtml"></div>
</div>
</el-drawer>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { formatDateTime } from '@/utils/datetime'
const props = defineProps({
modelValue: { type: Boolean, default: false },
model: { type: Object, default: null }
})
const emit = defineEmits(['update:modelValue'])
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''
const visible = ref(props.modelValue)
// 无正文时给出占位提示,避免抽屉内容区空白
const contentHtml = computed(
() => props.model?.content || '<p style="color:#999">暂无正文</p>'
)
watch(
() => props.modelValue,
(val) => {
visible.value = val
}
)
watch(visible, (val) => {
emit('update:modelValue', val)
})
const previewUrl = (url) => {
if (!url) return ''
if (/^https?:\/\//i.test(url)) return url
return API_BASE_URL + url
}
</script>
<style lang="less" scoped>
.preview {
padding: 0 8px 40px;
&__title {
margin: 0 0 12px;
font-size: 24px;
font-weight: 600;
line-height: 1.4;
}
&__meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
&__desc {
margin: 0 0 16px;
padding: 12px 14px;
background: var(--el-fill-color-light);
border-radius: 6px;
font-size: 13px;
line-height: 1.8;
color: var(--el-text-color-regular);
}
&__cover {
width: 100%;
max-height: 260px;
object-fit: cover;
border-radius: 6px;
margin-bottom: 16px;
}
&__trans {
margin-bottom: 16px;
font-size: 13px;
color: var(--el-text-color-secondary);
word-break: break-all;
}
&__content {
font-size: 15px;
line-height: 1.9;
color: var(--el-text-color-primary);
word-break: break-word;
:deep(img) {
max-width: 100%;
border-radius: 4px;
}
:deep(pre) {
padding: 12px 16px;
background: var(--el-fill-color-light);
border-radius: 4px;
overflow-x: auto;
}
:deep(table) {
width: 100%;
border-collapse: collapse;
th,
td {
border: 1px solid var(--el-border-color);
padding: 8px 12px;
}
}
}
}
</style>