m2pool_web_frontend/mining-pool/src/views/editorDocument.vue

760 lines
20 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>
<div class="editor-page">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<div class="title-section">
<i class="el-icon-edit-outline title-icon"></i>
<h1 class="page-title">wangeditor 富文本文档编辑器</h1>
</div>
<p class="page-description">使用富文本编辑器创建和管理文档支持文本格式化链接列表等功能</p>
</div>
<div class="header-actions">
<el-button type="info" icon="el-icon-view" @click="handlePreview">
预览文档
</el-button>
<el-button type="success" icon="el-icon-document" @click="handleSave">
保存文档
</el-button>
<el-button type="warning" icon="el-icon-upload2" @click="handelAddDocument">
发布文档
</el-button>
</div>
</div>
<!-- 文档配置区域 -->
<div class="config-section">
<div class="section-header">
<i class="el-icon-setting"></i>
<span>文档配置</span>
</div>
<div class="config-content">
<div class="config-row">
<div class="config-item">
<label class="required">文档标题</label>
<el-input
v-model="addParams.title"
placeholder="请输入文档标题"
@input="handleAutoSave"
size="medium"
/>
</div>
<div class="config-item">
<label class="required">文档类型</label>
<el-select
v-model="addParams.type"
placeholder="请选择文档类型"
@change="handleAutoSave"
size="medium"
>
<el-option
v-for="item in TypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</div>
</div>
</div>
<!-- 编辑器区域 -->
<div class="editor-section">
<div class="section-header">
<i class="el-icon-edit-outline"></i>
<span>富文本编辑器</span>
</div>
<div class="editor-container">
<Toolbar
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
style="border-bottom: 1px solid #ccc"
/>
<Editor
style="height: 600px; overflow-y: hidden;"
v-model="addParams.content"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
@onChange="handleEditorChange"
/>
</div>
</div>
<!-- 预览文档对话框 -->
<el-dialog
title="文档预览"
:visible.sync="previewVisible"
width="80%"
:before-close="handlePreviewClose"
class="preview-dialog"
>
<div class="preview-content">
<div v-html="addParams.content"></div>
<div v-if="!addParams.content" class="preview-empty">
<i class="el-icon-document"></i>
<p>暂无内容</p>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="previewVisible = false">关闭</el-button>
<el-button type="primary" @click="handlePrintPreview">打印预览</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Vue from "vue";
import E from "wangeditor";
import { addDocument } from '../api/documentManagement'
export default Vue.extend({
components: { Editor, Toolbar },
data() {
return {
editor: null,
html: '<p>hello</p>',
toolbarConfig: {
excludeKeys: [],
insertKeys: {
index: 0,
keys: ['bold', 'italic', 'underline', 'through', 'code', 'sub', 'sup', 'clearStyle']
}
},
editorConfig: {
placeholder: '请在此输入文档内容...',
MENU_CONF: {}
},
mode: 'default',
addParams:{
title:'',
content:'',
type:'',
lang:"",
imageUrl: '',
},
lastSaveTime: '',
autoSaveTimer: null,
isSaving: false, // 新增:控制自动保存提示的显示
previewVisible: false, // 新增:控制预览对话框的显示
currentTime: '', // 新增:记录预览时间
TypeList:[
// {
// value:"1",
// label:"服务条款"
// },
// {
// value:"2",
// label:"api文档"
// },
// {
// value:"3",
// label:"挖矿教程"
// },
// {
// value:"0",
// label:"其他"
// }
],
}
},
methods: {
async fetchAddDocument(params){
const res = await addDocument(params)
},
onCreated(editor) {
this.editor = Object.seal(editor)
// 编辑器创建后,如果有恢复的内容,设置到编辑器中
if (this.addParams.content) {
this.$nextTick(() => {
editor.setHtml(this.addParams.content)
})
}
},
handelAddDocument(){
if (!this.addParams.type) {
this.$message({
message: '请填写文档类型',
type: 'warning',
duration: 4000,
showClose: true
})
return
}
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
this.fetchAddDocument(this.addParams)
},
handlePreview() {
this.previewVisible = true
this.currentTime = new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
},
handlePreviewClose() {
this.previewVisible = false
},
handlePrintPreview() {
const printWindow = window.open('', '_blank');
if (printWindow) {
printWindow.document.write(`
<html>
<head>
<title>${this.addParams.title || '文档预览'}</title>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
margin: 20px;
}
.preview-empty {
text-align: center;
padding: 50px 0;
color: #95a5a6;
}
.preview-empty i {
font-size: 40px;
margin-bottom: 15px;
}
</style>
</head>
<body>
${this.addParams.content || '<div class="preview-empty"><i>📄</i><p>暂无内容</p></div>'}
</body>
</html>
`);
printWindow.document.close();
printWindow.print();
printWindow.close();
}
},
handleSave() {
this.manualSave()
this.$message({
message: '内容已保存到本地请尽快发布关闭页面可能丢失内容',
type: 'success',
duration: 4000,
showClose: true
})
},
handleEditorChange() {
this.handleAutoSave()
},
handleAutoSave() {
// 显示正在保存状态
this.isSaving = true
if (this.autoSaveTimer) {
clearTimeout(this.autoSaveTimer)
}
this.autoSaveTimer = setTimeout(() => {
this.saveToLocalStorage()
}, 1500) // 减少到1.5秒,提高响应速度
},
saveToLocalStorage() {
try {
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
const saveData = {
title: this.addParams.title,
content: this.addParams.content,
type: this.addParams.type,
imageUrl: this.addParams.imageUrl,
timestamp: new Date().toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}),
lastModified: Date.now()
}
localStorage.setItem('editor_draft', JSON.stringify(saveData))
this.lastSaveTime = saveData.timestamp
this.isSaving = false
} catch (error) {
console.error('自动保存失败:', error)
this.isSaving = false
}
},
loadFromLocalStorage() {
try {
const savedData = localStorage.getItem('editor_draft')
if (savedData) {
const data = JSON.parse(savedData)
// 检查数据是否过期7天
const isExpired = Date.now() - (data.lastModified || 0) > 7 * 24 * 60 * 60 * 1000
if (isExpired) {
console.log('本地数据已过期清除缓存')
localStorage.removeItem('editor_draft')
return
}
// 使用 $nextTick 确保 DOM 更新后再设置内容
this.$nextTick(() => {
this.addParams.title = data.title || ''
this.addParams.content = data.content || ''
this.addParams.type = data.type || '1'
this.addParams.imageUrl = data.imageUrl || ''
this.lastSaveTime = data.timestamp || ''
// 如果编辑器已经创建,直接设置内容
if (this.editor) {
this.editor.setHtml(data.content || '')
}
})
}
} catch (error) {
console.error('恢复数据失败:', error)
}
},
clearDraft() {
localStorage.removeItem('editor_draft')
this.lastSaveTime = ''
this.isSaving = false
},
// 新增:手动保存方法
manualSave() {
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
this.saveToLocalStorage()
},
// 页面卸载前处理
handleBeforeUnload(event) {
// 自动保存,不提示用户
if (this.addParams.title || this.addParams.content) {
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
this.saveToLocalStorage()
}
},
// 新增:获取文档类型名称
getDocumentTypeName(type) {
switch (type) {
case '1':
return '服务条款';
case '2':
return 'api文档';
case '3':
return '挖矿教程';
case '0':
return '其他';
default:
return '未知类型';
}
},
},
mounted() {
// 先恢复数据
this.loadFromLocalStorage()
// 添加页面卸载前的保存保护
window.addEventListener('beforeunload', this.handleBeforeUnload)
// 延迟设置默认内容,避免覆盖恢复的数据
setTimeout(() => {
if (!this.addParams.content) {
this.html = '<p>模拟 Ajax 异步设置内容 HTML</p>'
}
}, 1500)
},
beforeDestroy() {
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
this.saveToLocalStorage()
// 移除事件监听器
window.removeEventListener('beforeunload', this.handleBeforeUnload)
if (this.autoSaveTimer) {
clearTimeout(this.autoSaveTimer)
}
const editor = this.editor
if (editor == null) return
editor.destroy()
},
beforeRouteLeave(to, from, next) {
// 确保获取最新的编辑器内容
if (this.editor) {
this.addParams.content = this.editor.getHtml()
}
this.saveToLocalStorage()
next()
}
})
</script>
<style scoped>
/* 页面整体样式 */
.editor-page {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
min-height: 100vh;
}
/* 页面头部样式 */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: flex-start;
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
}
.header-left {
flex: 1;
}
.title-section {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.title-icon {
font-size: 28px;
margin-right: 12px;
color: #ffd700;
}
.page-title {
font-size: 28px;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.page-description {
font-size: 16px;
opacity: 0.9;
margin: 0;
line-height: 1.6;
}
.header-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.header-actions .el-button {
border-radius: 8px;
font-weight: 500;
padding: 12px 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transition: all 0.3s ease;
}
.header-actions .el-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
}
/* 配置区域样式 */
.config-section {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 2px solid #f8f9fa;
color: #2c3e50;
font-weight: 600;
font-size: 18px;
}
.section-header i {
margin-right: 10px;
color: #667eea;
font-size: 20px;
}
.config-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.config-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.config-item {
display: flex;
flex-direction: column;
}
.config-item label {
margin-bottom: 8px;
font-weight: 600;
color: #495057;
font-size: 14px;
}
.required::after {
content: '*';
color: #e74c3c;
margin-left: 4px;
font-weight: bold;
}
/* 编辑器区域样式 */
.editor-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border: 1px solid #e9ecef;
}
.editor-container {
border: 1px solid #dcdfe6;
border-radius: 8px;
overflow: hidden;
background: white;
min-height: 650px;
}
/* 设置编辑器高度 */
.editor-container .w-e-text-container {
height: 600px !important;
min-height: 600px !important;
}
/* 编辑器内容区域样式 */
.editor-container .w-e-text {
height: 600px !important;
min-height: 600px !important;
}
/* 保存状态样式 */
.save-status {
position: fixed;
bottom: 20px;
right: 20px;
background: #67c23a;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(103, 194, 58, 0.3);
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
z-index: 1000;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 预览对话框样式 */
.preview-dialog .el-dialog__body {
padding: 20px;
max-height: 70vh;
overflow-y: auto;
}
.preview-dialog .el-dialog__header {
background-color: #f8f9fa;
border-bottom: 1px solid #ebeef5;
}
.preview-dialog .el-dialog__header .el-dialog__title {
color: #333;
font-weight: 600;
}
.preview-dialog .el-dialog__footer {
background-color: #f8f9fa;
border-top: 1px solid #ebeef5;
padding: 15px 20px;
}
.preview-dialog .el-dialog__footer .dialog-footer {
text-align: right;
}
.preview-dialog .el-dialog__footer .dialog-footer .el-button {
border-radius: 6px;
padding: 8px 15px;
font-weight: 500;
}
.preview-dialog .el-dialog__footer .dialog-footer .el-button:first-child {
margin-right: 10px;
}
.preview-dialog .el-dialog__footer .dialog-footer .el-button:last-child {
background-color: #667eea;
border-color: #667eea;
}
.preview-dialog .el-dialog__footer .dialog-footer .el-button:last-child:hover {
background-color: #5a67d8;
border-color: #5a67d8;
}
/* 预览内容样式 - 简化版本 */
.preview-content {
background: white;
padding: 20px;
}
.preview-empty {
text-align: center;
padding: 50px 20px;
color: #95a5a6;
}
.preview-empty i {
font-size: 48px;
margin-bottom: 15px;
display: block;
}
.preview-empty p {
margin: 0;
font-size: 16px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.editor-page {
padding: 10px;
}
.page-header {
flex-direction: column;
gap: 20px;
padding: 20px;
}
.header-actions {
width: 100%;
justify-content: center;
}
.config-row {
grid-template-columns: 1fr;
gap: 16px;
}
.page-title {
font-size: 24px;
}
.save-status {
bottom: 10px;
right: 10px;
left: 10px;
text-align: center;
justify-content: center;
}
}
/* Element UI 组件样式优化 */
.el-input, .el-select {
border-radius: 8px;
}
.el-input__inner, .el-select .el-input__inner {
border-radius: 8px;
border: 1px solid #dcdfe6;
transition: all 0.3s ease;
}
.el-input__inner:focus, .el-select .el-input__inner:focus {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
.el-tag {
border-radius: 6px;
font-weight: 500;
}
</style>