补充页面
This commit is contained in:
209
src/components/XMLwang.vue
Normal file
209
src/components/XMLwang.vue
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<div class="xml-importer">
|
||||||
|
<input type="file" @change="handleFileUpload" accept=".xml" />
|
||||||
|
<button @click="importToEditor" :disabled="!xmlData">导入到编辑器</button>
|
||||||
|
<div ref="editor"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import E from 'wangeditor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'XmlImporter',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
xmlData: null,
|
||||||
|
parsedContent: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initEditor()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initEditor() {
|
||||||
|
this.editor = new E(this.$refs.editor)
|
||||||
|
this.editor.create()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理XML文件上传
|
||||||
|
handleFileUpload(event) {
|
||||||
|
const file = event.target.files[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (e) => {
|
||||||
|
this.xmlData = e.target.result
|
||||||
|
this.parseS1000DXml(this.xmlData)
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 解析S1000D XML
|
||||||
|
parseS1000DXml(xmlString) {
|
||||||
|
try {
|
||||||
|
const parser = new DOMParser()
|
||||||
|
const xmlDoc = parser.parseFromString(xmlString, "text/xml")
|
||||||
|
|
||||||
|
// 检查是否是有效的S1000D文档
|
||||||
|
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
|
||||||
|
throw new Error('XML解析错误: ' + xmlDoc.getElementsByTagName('parsererror')[0].textContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取内容部分
|
||||||
|
const contentElement = xmlDoc.querySelector('content description')
|
||||||
|
if (!contentElement) {
|
||||||
|
throw new Error('未找到content/description元素')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将S1000D XML转换为HTML
|
||||||
|
this.parsedContent = this.s1000dToHtml(contentElement)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('XML解析失败:', error)
|
||||||
|
alert(`解析失败: ${error.message}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 将S1000D元素转换为HTML
|
||||||
|
s1000dToHtml(s1000dElement) {
|
||||||
|
let html = ''
|
||||||
|
|
||||||
|
// 递归处理子节点
|
||||||
|
const processNode = (node) => {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
return node.textContent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return ''
|
||||||
|
|
||||||
|
const tag = node.tagName.toLowerCase()
|
||||||
|
const children = Array.from(node.childNodes).map(processNode).join('')
|
||||||
|
const attrs = Array.from(node.attributes)
|
||||||
|
.map(attr => ` ${attr.name}="${this.escapeHtml(attr.value)}"`)
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
// 将S1000D标签映射回HTML
|
||||||
|
switch(tag) {
|
||||||
|
case 'para': {
|
||||||
|
return `<p${attrs}>${children}</p>`
|
||||||
|
}
|
||||||
|
case 'emphasis': {
|
||||||
|
const role = node.getAttribute('role') || 'bold'
|
||||||
|
switch(role) {
|
||||||
|
case 'bold': return `<strong${attrs}>${children}</strong>`
|
||||||
|
case 'italic': return `<em${attrs}>${children}</em>`
|
||||||
|
case 'underline': return `<u${attrs}>${children}</u>`
|
||||||
|
default: return `<span class="emphasis-${role}"${attrs}>${children}</span>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'orderedlist': {
|
||||||
|
return `<ol${attrs}>${children}</ol>`
|
||||||
|
}
|
||||||
|
case 'itemizedlist': {
|
||||||
|
return `<ul${attrs}>${children}</ul>`
|
||||||
|
}
|
||||||
|
case 'listitem': {
|
||||||
|
return `<li${attrs}>${children}</li>`
|
||||||
|
}
|
||||||
|
case 'figure': {
|
||||||
|
const graphic = node.querySelector('graphic')
|
||||||
|
if (graphic) {
|
||||||
|
const imgAttrs = Array.from(graphic.attributes)
|
||||||
|
.filter(attr => attr.name !== 'infoentityid')
|
||||||
|
.map(attr => ` ${attr.name}="${this.escapeHtml(attr.value)}"`)
|
||||||
|
.join('')
|
||||||
|
return `<div class="figure"><img${imgAttrs}>${children}</div>`
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
case 'table': {
|
||||||
|
return `<table${attrs}>${children}</table>`
|
||||||
|
}
|
||||||
|
case 'row': {
|
||||||
|
return `<tr${attrs}>${children}</tr>`
|
||||||
|
}
|
||||||
|
case 'entry': {
|
||||||
|
const isHeader = node.getAttribute('thead') === 'yes'
|
||||||
|
return isHeader
|
||||||
|
? `<th${attrs}>${children}</th>`
|
||||||
|
: `<td${attrs}>${children}</td>`
|
||||||
|
}
|
||||||
|
case 'title': {
|
||||||
|
const level = node.getAttribute('level') || '1'
|
||||||
|
return `<h${level}${attrs}>${children}</h${level}>`
|
||||||
|
}
|
||||||
|
case 'xref': {
|
||||||
|
const href = node.getAttribute('xrefid') || '#'
|
||||||
|
return `<a href="${href}"${attrs}>${children}</a>`
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// 未知标签作为div处理
|
||||||
|
return `<div class="s1000d-${tag}"${attrs}>${children}</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理所有子节点
|
||||||
|
html = Array.from(s1000dElement.childNodes).map(processNode).join('')
|
||||||
|
|
||||||
|
return html
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML特殊字符转义
|
||||||
|
escapeHtml(text) {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 将解析后的内容导入编辑器
|
||||||
|
importToEditor() {
|
||||||
|
if (this.parsedContent && this.editor) {
|
||||||
|
this.editor.txt.html(this.parsedContent)
|
||||||
|
alert('内容已成功导入编辑器')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.xml-importer {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xml-importer input[type="file"] {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xml-importer button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #42b983;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xml-importer button:disabled {
|
||||||
|
background: #cccccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.figure {
|
||||||
|
margin: 15px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
425
src/components/wang copy.vue
Normal file
425
src/components/wang copy.vue
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
<template>
|
||||||
|
<div class="editor-container">
|
||||||
|
<div class="toolbar">
|
||||||
|
<button @click="insertImage">上传图片</button>
|
||||||
|
<button @click="insertVideo">上传视频</button>
|
||||||
|
<button @click="loadAllContent">加载所有内容</button>
|
||||||
|
<button @click="generateTOC">生成目录</button>
|
||||||
|
<button @click="deleteAllVideos">删除所有视频</button>
|
||||||
|
<button @click="loadFwb">模拟加载</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="main" class="content-preview"></div>
|
||||||
|
<div id="nav" class="toc-container"></div>
|
||||||
|
<div id="editor" ref="editor"></div>
|
||||||
|
<!-- <div id="editor2" ref="editor2"></div> -->
|
||||||
|
<!-- <button @click="callWpfMethod">调用WPF方法</button> -->
|
||||||
|
<input type="text" v-model="val1">
|
||||||
|
<input type="text" v-model="val2">
|
||||||
|
<button @click="sendMessageToHost('李四','张三')">传递</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WangEditor from 'wangeditor'
|
||||||
|
// import axios from 'axios';
|
||||||
|
// window.handleMessageFromDotNet = function(msg) {
|
||||||
|
// alert("Received message from C#: " + msg);
|
||||||
|
// }
|
||||||
|
export default {
|
||||||
|
name: 'RichTextEditor',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
val1:'',
|
||||||
|
val2:'',
|
||||||
|
wpfData: '',
|
||||||
|
message: '',
|
||||||
|
editor: null,
|
||||||
|
editor2:null,
|
||||||
|
editorContent: '',
|
||||||
|
editorContent2:'',
|
||||||
|
hasVideoSelected: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initEditor()
|
||||||
|
// this.initEditor2();
|
||||||
|
// window.receiveMessageFromWpf = this.receiveMessageFromWpf;
|
||||||
|
|
||||||
|
console.log('chrome对象是否存在:', !!window.chrome);
|
||||||
|
console.log('webview对象是否存在:', !!window.chrome?.webview);
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 销毁编辑器
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sendMessageToHost() {
|
||||||
|
this.$sendToDotNet(this.val1,this.val2);
|
||||||
|
|
||||||
|
},
|
||||||
|
loadFwb() {
|
||||||
|
this.editor2.txt.html(this.editorContent)
|
||||||
|
|
||||||
|
// 创建编辑器
|
||||||
|
|
||||||
|
},
|
||||||
|
initEditor() {
|
||||||
|
this.editor = new WangEditor(this.$refs.editor)
|
||||||
|
|
||||||
|
// 配置编辑器
|
||||||
|
this.editor.config.uploadImgShowBase64 = true // 使用 base64 保存图片
|
||||||
|
this.editor.config.onchange = (html) => {
|
||||||
|
this.editorContent = html
|
||||||
|
}
|
||||||
|
let dm = this.editor.config.menus;
|
||||||
|
console.log("默认菜单",dm)
|
||||||
|
|
||||||
|
// 完全自定义菜单
|
||||||
|
this.editor.config.menus = [
|
||||||
|
'head', // 标题
|
||||||
|
'bold', // 粗体
|
||||||
|
// 'fontSize',//字号
|
||||||
|
// 'fontName',//字体
|
||||||
|
'italic', // 斜体
|
||||||
|
'underline', // 下划线
|
||||||
|
'strikeThrough', // 删除线
|
||||||
|
// 'line',//行高
|
||||||
|
'lineHeight',//
|
||||||
|
// 'foreColor', // 文字颜色
|
||||||
|
// 'backColor', // 背景颜色
|
||||||
|
// 'link', // 链接
|
||||||
|
'list', // 列表
|
||||||
|
// 'todo',//
|
||||||
|
'justify', // 对齐方式
|
||||||
|
// 'quote', // 引用
|
||||||
|
// 'emoticon',//表情
|
||||||
|
'image', // 图片
|
||||||
|
'video', // 视频
|
||||||
|
'table', // 表格
|
||||||
|
// 'code', // 代码
|
||||||
|
'splitLine',//分割线
|
||||||
|
'undo', // 撤销
|
||||||
|
'redo' // 重做
|
||||||
|
]
|
||||||
|
|
||||||
|
// 创建编辑器
|
||||||
|
this.editor.create();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// this.editor2 = new WangEditor(this.$refs.editor2)
|
||||||
|
|
||||||
|
// // 配置编辑器
|
||||||
|
// this.editor2.config.uploadImgShowBase64 = true // 使用 base64 保存图片
|
||||||
|
// this.editor2.config.onchange = (html) => {
|
||||||
|
// console.log("22222",html)
|
||||||
|
// // this.editorContent2 = html
|
||||||
|
// }
|
||||||
|
// this.editor2.create()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 上传图片
|
||||||
|
insertImage() {
|
||||||
|
const imgUrl = 'http://youneed.top:10017/uploads/1.jpg'
|
||||||
|
this.editor.cmd.do('insertHTML', `<img src="${imgUrl}" style="max-width: 100%;" alt="图片">`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 插入视频(可靠版本)
|
||||||
|
insertVideo() {
|
||||||
|
const videoUrl = 'http://youneed.top:10017/uploads/video.mp4'
|
||||||
|
const videoId = `video-${Date.now()}`
|
||||||
|
|
||||||
|
// 创建视频HTML
|
||||||
|
const videoHtml = `
|
||||||
|
<div class="video-wrapper" data-video-id="${videoId}">
|
||||||
|
<video controls width="50%" style='margin:auto' data-video-id="${videoId}">
|
||||||
|
<source src="${videoUrl}" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
<div class="video-controls">
|
||||||
|
<span class="video-delete" data-video-id="${videoId}">× 删除</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p><br></p>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 使用编辑器命令插入
|
||||||
|
this.editor.cmd.do('insertHTML', videoHtml)
|
||||||
|
|
||||||
|
// 添加删除事件监听
|
||||||
|
this.bindDel();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 根据ID删除视频(修正版)
|
||||||
|
deleteVideoById(videoId) {
|
||||||
|
console.log("ID",videoId)
|
||||||
|
const videoWrapper = document.querySelector(`[data-video-id="${videoId}"]`)
|
||||||
|
if (videoWrapper) {
|
||||||
|
// 获取视频所在段落
|
||||||
|
let parent = videoWrapper.parentElement
|
||||||
|
while (parent && parent !== this.$refs.editor) {
|
||||||
|
if (parent.tagName === 'P') {
|
||||||
|
// 如果是空段落,一并删除
|
||||||
|
if (parent.textContent.trim() === '') {
|
||||||
|
parent.remove()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
parent = parent.parentElement
|
||||||
|
}
|
||||||
|
|
||||||
|
videoWrapper.remove()
|
||||||
|
|
||||||
|
// 修正:使用正确的方式通知内容变更
|
||||||
|
if (this.editor.txt) {
|
||||||
|
this.editor.txt.html(this.editor.txt.html()) // 强制更新编辑器内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
bindDel(){
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const deleteButtons = document.querySelectorAll('.video-delete')
|
||||||
|
console.log("所有绑定:",deleteButtons)
|
||||||
|
deleteButtons.forEach(btn => {
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const videoId = btn.getAttribute('data-video-id')
|
||||||
|
this.deleteVideoById(videoId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deleteAllVideos() {
|
||||||
|
const videoWrappers = document.querySelectorAll('.video-wrapper')
|
||||||
|
if (videoWrappers.length === 0) {
|
||||||
|
alert('没有找到可删除的视频')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
videoWrappers.forEach(wrapper => {
|
||||||
|
wrapper.remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 删除可能残留的空段落
|
||||||
|
const editor = this.$refs.editor
|
||||||
|
const paragraphs = editor.querySelectorAll('p')
|
||||||
|
paragraphs.forEach(p => {
|
||||||
|
if (p.textContent.trim() === '' && p.children.length === 0) {
|
||||||
|
p.remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 修正:使用正确的方式通知内容变更
|
||||||
|
if (this.editor.txt) {
|
||||||
|
this.editor.txt.html(this.editor.txt.html()) // 强制更新编辑器内容
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`已删除 ${videoWrappers.length} 个视频`)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 加载所有内容到 main 容器
|
||||||
|
loadAllContent() {
|
||||||
|
const mainContainer = document.getElementById('main')
|
||||||
|
mainContainer.innerHTML = this.editorContent;
|
||||||
|
console.log("this.editorContent",this.editorContent)
|
||||||
|
// 为main容器中的标题添加ID
|
||||||
|
this.addHeadingIds(mainContainer)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// 为标题元素添加ID
|
||||||
|
addHeadingIds(container) {
|
||||||
|
const headings = container.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||||
|
headings.forEach((heading, index) => {
|
||||||
|
if (!heading.id) {
|
||||||
|
heading.id = `heading-${index}-${Date.now()}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成带锚点的目录
|
||||||
|
generateTOC() {
|
||||||
|
const navContainer = document.getElementById('nav')
|
||||||
|
navContainer.innerHTML = '' // 清空原有目录
|
||||||
|
|
||||||
|
// 确保main容器已加载内容
|
||||||
|
if (!document.getElementById('main').innerHTML) {
|
||||||
|
this.loadAllContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainContainer = document.getElementById('main')
|
||||||
|
const headings = mainContainer.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||||
|
|
||||||
|
if (headings.length === 0) {
|
||||||
|
navContainer.innerHTML = '<p>没有找到标题元素,无法生成目录。</p>'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const tocList = document.createElement('ul')
|
||||||
|
tocList.style.listStyleType = 'none'
|
||||||
|
tocList.style.paddingLeft = '0'
|
||||||
|
|
||||||
|
headings.forEach(heading => {
|
||||||
|
const level = parseInt(heading.tagName.substring(1))
|
||||||
|
const listItem = document.createElement('li')
|
||||||
|
listItem.style.marginLeft = `${(level - 1) * 15}px`
|
||||||
|
listItem.style.marginBottom = '5px'
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = `#${heading.id}`
|
||||||
|
link.textContent = heading.textContent
|
||||||
|
link.style.textDecoration = 'none'
|
||||||
|
link.style.color = '#333'
|
||||||
|
|
||||||
|
// 添加平滑滚动效果
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
document.getElementById(heading.id).scrollIntoView({
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
listItem.appendChild(link)
|
||||||
|
tocList.appendChild(listItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
const tocContainer = document.createElement('div')
|
||||||
|
tocContainer.style.border = '1px solid #ddd'
|
||||||
|
tocContainer.style.padding = '15px'
|
||||||
|
tocContainer.style.marginBottom = '20px'
|
||||||
|
tocContainer.style.background = '#f9f9f9'
|
||||||
|
tocContainer.style.borderRadius = '4px'
|
||||||
|
|
||||||
|
const tocTitle = document.createElement('h2')
|
||||||
|
tocTitle.textContent = '目录'
|
||||||
|
tocTitle.style.marginTop = '0'
|
||||||
|
|
||||||
|
tocContainer.appendChild(tocTitle)
|
||||||
|
tocContainer.appendChild(tocList)
|
||||||
|
navContainer.appendChild(tocContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
// 方式1:直接访问全局变量
|
||||||
|
this.$watch(
|
||||||
|
() => this.$dotNetMessage,
|
||||||
|
(newMsg) => {
|
||||||
|
console.log('收到消息:', newMsg)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 方式2:监听全局事件
|
||||||
|
window.addEventListener('dotnet-message', (e) => {
|
||||||
|
console.log('通过事件收到:', e.detail)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.editor-container {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button {
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar button:hover {
|
||||||
|
background: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor,#editor2 {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
padding: 15px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
#main th,
|
||||||
|
#main td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
#main th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 视频容器样式 */
|
||||||
|
.video-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin: 15px 0;
|
||||||
|
/* border: 1px solid #ddd; */
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-delete {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: rgba(255, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-delete:hover {
|
||||||
|
background: rgba(255, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保视频响应式 */
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
207
src/components/wangXLM.vue
Normal file
207
src/components/wangXLM.vue
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<template>
|
||||||
|
<div class="s1000d-container">
|
||||||
|
<button @click="exportToS1000D">导出为S1000D XML</button>
|
||||||
|
<div ref="editor"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import E from 'wangeditor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
content: '<p>这是初始内容</p>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initEditor()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initEditor() {
|
||||||
|
this.editor = new E(this.$refs.editor)
|
||||||
|
this.editor.config.onchange = (html) => {
|
||||||
|
this.content = html
|
||||||
|
}
|
||||||
|
this.editor.create()
|
||||||
|
this.editor.txt.html(this.content)
|
||||||
|
},
|
||||||
|
|
||||||
|
exportToS1000D() {
|
||||||
|
// 1. 获取富文本内容
|
||||||
|
const htmlContent = this.content || this.editor.txt.html()
|
||||||
|
|
||||||
|
// 2. 转换为S1000D XML结构
|
||||||
|
const s1000dXml = this.convertToS1000D(htmlContent)
|
||||||
|
|
||||||
|
// 3. 下载XML文件
|
||||||
|
this.downloadXML(s1000dXml, 's1000d-data-module.xml')
|
||||||
|
},
|
||||||
|
|
||||||
|
convertToS1000D(html) {
|
||||||
|
// 基本S1000D XML结构
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE dmodule [
|
||||||
|
<!ENTITY % ISOEntities PUBLIC "ISO 8879-1986//ENTITIES ISO Character Entities 20030531//EN" "ISOEntities">
|
||||||
|
%ISOEntities;
|
||||||
|
]>
|
||||||
|
<dmodule xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="http://www.s1000d.org/S1000D_4-1/xml_schema_flat/descript.xsd">
|
||||||
|
|
||||||
|
<!-- 标识和状态部分 -->
|
||||||
|
<idstatus>
|
||||||
|
<dmaddress>
|
||||||
|
<dmc>
|
||||||
|
<avee>AVE</avee>
|
||||||
|
<modelic>MODEL</modelic>
|
||||||
|
<sdc>A</sdc>
|
||||||
|
<chapnum>00</chapnum>
|
||||||
|
<section>0</section>
|
||||||
|
<subsect>0</subsect>
|
||||||
|
<subject>SUB</subject>
|
||||||
|
<discode>DISC</discode>
|
||||||
|
<discodev>001</discodev>
|
||||||
|
<incode>INC</incode>
|
||||||
|
<incodev>001</incodev>
|
||||||
|
<itemloc>ITEM</itemloc>
|
||||||
|
</dmc>
|
||||||
|
<issue inwork="01" issno="001" type="new"/>
|
||||||
|
<language language-iso="zh-CN"/>
|
||||||
|
<dmtitle>
|
||||||
|
<techname>技术文档标题</techname>
|
||||||
|
<infoname>信息名称</infoname>
|
||||||
|
</dmtitle>
|
||||||
|
</dmaddress>
|
||||||
|
</idstatus>
|
||||||
|
|
||||||
|
<!-- 内容部分 -->
|
||||||
|
<content>
|
||||||
|
<description>
|
||||||
|
${this.htmlToS1000D(html)}
|
||||||
|
</description>
|
||||||
|
</content>
|
||||||
|
</dmodule>`
|
||||||
|
},
|
||||||
|
|
||||||
|
// 将HTML转换为S1000D兼容的XML
|
||||||
|
htmlToS1000D(html) {
|
||||||
|
// 创建临时div来解析HTML
|
||||||
|
const tempDiv = document.createElement('div')
|
||||||
|
tempDiv.innerHTML = html
|
||||||
|
|
||||||
|
// 递归处理DOM节点
|
||||||
|
const processNode = (node) => {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
return this.escapeXml(node.textContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return ''
|
||||||
|
|
||||||
|
const tag = node.tagName.toLowerCase()
|
||||||
|
const children = Array.from(node.childNodes).map(processNode).join('')
|
||||||
|
const attrs = Array.from(node.attributes)
|
||||||
|
.map(attr => ` ${attr.name}="${this.escapeXml(attr.value)}"`)
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
// 将HTML标签映射到S1000D元素
|
||||||
|
switch(tag) {
|
||||||
|
case 'p':
|
||||||
|
return `<para${attrs}>${children}</para>`
|
||||||
|
case 'b':
|
||||||
|
case 'strong':
|
||||||
|
return `<emphasis role="bold">${children}</emphasis>`
|
||||||
|
case 'i':
|
||||||
|
case 'em':
|
||||||
|
return `<emphasis role="italic">${children}</emphasis>`
|
||||||
|
case 'u':
|
||||||
|
return `<emphasis role="underline">${children}</emphasis>`
|
||||||
|
case 'ol':
|
||||||
|
return `<orderedlist${attrs}>${children}</orderedlist>`
|
||||||
|
case 'ul':
|
||||||
|
return `<itemizedlist${attrs}>${children}</itemizedlist>`
|
||||||
|
case 'li':
|
||||||
|
return `<listitem>${children}</listitem>`
|
||||||
|
case 'img': {
|
||||||
|
const figureId = `FIG_${Date.now()}`
|
||||||
|
return `<figure><graphic infoentityid="${figureId}"${attrs}/></figure>`
|
||||||
|
}
|
||||||
|
case 'table':
|
||||||
|
return `<table${attrs}>${children}</table>`
|
||||||
|
case 'tr':
|
||||||
|
return `<row>${children}</row>`
|
||||||
|
case 'td':
|
||||||
|
return `<entry>${children}</entry>`
|
||||||
|
case 'th':
|
||||||
|
return `<entry thead="yes">${children}</entry>`
|
||||||
|
case 'h1':
|
||||||
|
case 'h2':
|
||||||
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
|
case 'h6': {
|
||||||
|
const level = parseInt(tag.substring(1))
|
||||||
|
return `<title level="${level}">${children}</title>`
|
||||||
|
}
|
||||||
|
case 'a':
|
||||||
|
return `<xref xrefid="${node.getAttribute('href') || ''}">${children}</xref>`
|
||||||
|
default:
|
||||||
|
return children // 忽略不支持的标签但保留内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(tempDiv.childNodes).map(processNode).join('')
|
||||||
|
},
|
||||||
|
|
||||||
|
// XML特殊字符转义
|
||||||
|
escapeXml(text) {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载XML文件
|
||||||
|
downloadXML(xmlString, fileName) {
|
||||||
|
const blob = new Blob([xmlString], { type: 'application/xml' })
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.s1000d-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #0066cc;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #0052a3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user