You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class = "flow-container">
<div class="post-editor">
<!-- 顶部工具栏 -->
<div class="toolbar">
<router-link to = '/personal/postManager'>
<el-button :icon="ArrowLeft" circle></el-button>
</router-link>
<el-select v-model="selectedCategory" placeholder="选择分区" class="category-select" size="large">
<el-option
v-for="item in categories"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="icon-group">
<el-button :icon="Document " circle size="large"></el-button>
<el-button :icon="Picture" circle size="large" @click="handlePicture"></el-button>
<el-button :icon="ArrowLeft" circle size="large"></el-button>
<el-button :icon="ArrowRight" circle size="large"></el-button>
</div>
<div class="action-buttons">
<button class = "btn-primary btn">保存草稿</button>
<button class = "btn-secondary btn">定时发布</button>
<button class = "btn-primary btn">发布帖子</button>
</div>
</div>
</div>
<!-- 图片上传对话框 -->
<el-dialog v-model="uploadPictureDialog" title="上传图片" :show-close="false">
<el-upload
:auto-upload="false"
:show-file-list="false"
:on-change="handleImageUpload"
accept="image/*"
class="upload-dialog"
>
<img v-if="previewUrl" :src="previewUrl" class="avatar-preview"/>
<el-icon size = 30px v-else><Plus/></el-icon>
</el-upload>
<template #footer>
<button class = "btn btn-primary" @click="uploadPictureDialog = false ,previewUrl = ''">确定</button>
</template>
</el-dialog>
<!-- 编辑器主体区域 -->
<div class="editor-body">
<!-- 左侧Markdown编辑框 -->
<div class="editor-pane card">
<el-input
v-model="title"
placeholder="请输入文章标题"
class="editor-title"
/>
<el-input
id="markdown-editor"
v-model="markdownText"
type="textarea"
:autosize="{ minRows: 20 }"
placeholder="正文"
class="markdown-input"
ref="elInputRef"
@click="saveCursor"
@keyup="saveCursor"
/>
</div>
<!-- 右侧Markdown预览框 -->
<div class="preview-pane card">
<div class="editor-title">{{ title || '请输入文章标题' }}</div>
<MarkdownRender :content="markdownText" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { marked } from 'marked'
import hljs from 'highlight.js'
import type { UploadFile } from 'element-plus'
import { markedHighlight } from 'marked-highlight'
import 'highlight.js/styles/github.css'
import { ArrowLeft, Document, Picture, ArrowRight } from '@element-plus/icons-vue'
import MarkdownRender from '@/components/MarkdownRender.vue'
import { useRoute } from 'vue-router'
import DOMPurify from 'dompurify'
const title = ref('')
const markdownText = ref('')
const elInputRef = ref();
const compiledMarkdown = ref('')
const selectedCategory = ref(null)
const categories = [
{ label: '分区1', value: 1 },
{ label: '分区2', value: 2 },
{ label: '分区3', value: 3 },
{ label: '分区4', value: 4 },
{ label: '分区5', value: 5 },
{ label: '分区6', value: 6 },
]
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight: (code: string, lang: string) => {
return hljs.highlightAuto(code, [lang]).value
},
})
)
watch(markdownText, async () => {
compiledMarkdown.value = await marked.parse(markdownText.value)
})
//处理图片上传
const uploadPictureDialog = ref(false)
const handlePicture = () => {
uploadPictureDialog.value = true
}
const previewUrl = ref('');
function handleImageUpload(rawFile: UploadFile) {
const file = rawFile.raw; // 获取原始 File 对象
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result as string;
insertAtCursor(`\n![image](${base64})\n`);
previewUrl.value = base64;
};
reader.readAsDataURL(file);
}
//图片插入Markdown的函数插入规则为插入光标所在处
let cursorPos = 0;
//记录光标位置
function saveCursor() {
const textarea = elInputRef.value?.$el.querySelector('textarea');
if (textarea) {
cursorPos = textarea.selectionStart;
}
}
//插入Markdown
function insertAtCursor(text: string) {
const textarea = elInputRef.value?.$el.querySelector('textarea');
if (!textarea) return;
const current = markdownText.value;
markdownText.value =
current.slice(0, cursorPos) + text + current.slice(cursorPos);
}
</script>
<style scoped lang="scss">
.post-editor {
position:fixed;
top:0;
left:0;
right:0;
height:60px;
padding-right:50px;
padding-left: 16px;
padding-bottom: 16px;
padding-top:16px;
background: #ead1fb;
z-index: 1000;
.toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
.el-button{
height: 55px;
width:55px;
::v-deep(.el-icon)
{
font-size:24px;
}
}
.category-select {
width: 300px;
}
.icon-group {
display: flex;
gap: 6px;
margin-left: 10px;
}
.title-input {
flex-grow: 1;
max-width: 400px;
}
.title-length {
color: #999;
margin-left: 8px;
}
.action-buttons {
margin-left: auto;
display: flex;
gap: 6px;
}
}
}
/* */
.upload-dialog {
min-width: 95%;
min-height:300px;
margin:10px;
margin-left:20px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
align-self: center;
.el-dialog__header {
background-color: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
}
.el-dialog__body {
border: 1px dashed #d9d9d9;
border-radius: 6px;
padding: 20px;
}
.avatar-preview {
width: 900px;
height: 500px;
object-fit: cover;
}
}
.editor-body {
display: flex;
flex-direction:row;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
margin-top:20px;
min-height: 100%;
.editor-pane,
.preview-pane {
min-height:93%;
width:50%;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.05);
}
.editor-title {
height:50px;
font-weight: bold;
margin-bottom: 16px;
font-size: 25px;
}
.markdown-input {
font-size:20px;
width: 100%;
::v-deep(.el-textarea__inner) {
border:none !important;
box-shadow:none !important;
}
}
}
</style>