2025-07-25 13:28:47 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="editor-container">
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<button @click="insertImage">上传图片</button>
|
|
|
|
|
|
<button @click="insertVideo">上传视频</button>
|
|
|
|
|
|
<button @click="loadAllContent">加载所有内容</button>
|
|
|
|
|
|
<button @click="generateTOC">生成目录</button>
|
2025-07-28 17:01:13 +08:00
|
|
|
|
<input type="color">
|
|
|
|
|
|
<!-- <button @click="deleteAllVideos">删除所有视频</button> -->
|
2025-07-25 13:28:47 +08:00
|
|
|
|
<button @click="loadFwb">模拟加载</button>
|
2025-07-28 17:01:13 +08:00
|
|
|
|
<!-- <button @click='test'>测试</button> -->
|
2025-07-25 13:28:47 +08:00
|
|
|
|
</div>
|
2025-07-28 17:01:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="s1000d-editor-container">
|
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<button @click="exportToS1000D" class="tool-btn">导出S1000D XML</button>
|
|
|
|
|
|
<label for="xml-upload" class="tool-btn">导入S1000D XML</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
id="xml-upload"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept=".xml"
|
|
|
|
|
|
@change="handleS1000DUpload"
|
|
|
|
|
|
style="display: none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button @click="validateCurrentXml" class="tool-btn">验证XML</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 编辑器区域 -->
|
|
|
|
|
|
<div class="editor-area" style="display:none;">
|
|
|
|
|
|
<div ref="editor" class="editor"></div>
|
|
|
|
|
|
<div class="xml-preview" style="display: none">
|
|
|
|
|
|
<h3>XML预览</h3>
|
|
|
|
|
|
<pre>{{ formattedXmlPreview }}</pre>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-25 13:28:47 +08:00
|
|
|
|
<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'
|
2025-07-28 17:01:13 +08:00
|
|
|
|
// import '@yaireo/colorpicker/dist/colorpicker.min.css';
|
|
|
|
|
|
// import '@yaireo/colorpicker'
|
2025-07-25 13:28:47 +08:00
|
|
|
|
// 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,
|
2025-07-28 17:01:13 +08:00
|
|
|
|
xmlPreview: '',
|
|
|
|
|
|
showValidationModal: false,
|
|
|
|
|
|
validationResult: {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '',
|
|
|
|
|
|
details: ''
|
|
|
|
|
|
},
|
|
|
|
|
|
// 简单的S1000D模板配置
|
|
|
|
|
|
dmConfig: {
|
|
|
|
|
|
dmc: {
|
|
|
|
|
|
modelIdentCode: 'AAA',
|
|
|
|
|
|
systemDiffCode: 'BBB',
|
|
|
|
|
|
systemCode: 'CCC',
|
|
|
|
|
|
subSystemCode: 'DDD',
|
|
|
|
|
|
subSubSystemCode: 'EEE',
|
|
|
|
|
|
assyCode: 'FFF',
|
|
|
|
|
|
disassyCode: 'GGG',
|
|
|
|
|
|
disassyCodeVariant: 'HHH',
|
|
|
|
|
|
infoCode: 'III',
|
|
|
|
|
|
infoCodeVariant: 'JJJ',
|
|
|
|
|
|
itemLocationCode: 'KKK'
|
|
|
|
|
|
},
|
|
|
|
|
|
issueInfo: {
|
|
|
|
|
|
issueNumber: '001',
|
|
|
|
|
|
inWork: '01',
|
|
|
|
|
|
issueDate: new Date().toISOString().split('T')[0]
|
|
|
|
|
|
},
|
|
|
|
|
|
language: 'zh-CN'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
formattedXmlPreview() {
|
|
|
|
|
|
if (!this.xmlPreview) return '暂无XML预览';
|
|
|
|
|
|
|
|
|
|
|
|
// 简单格式化XML显示
|
|
|
|
|
|
return this.xmlPreview
|
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
.replace(/\n/g, '<br>')
|
|
|
|
|
|
.replace(/\s/g, ' ');
|
2025-07-25 13:28:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
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: {
|
|
|
|
|
|
test() {
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
2025-07-28 17:01:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 启用颜色选择功能
|
|
|
|
|
|
this.editor.config.colors = [
|
|
|
|
|
|
'#000000', '#ffffff', '#eeeef1',
|
|
|
|
|
|
'#ff0000', '#ff5e5e', '#ffbbbb',
|
|
|
|
|
|
'#0033ff', '#0055ff', '#3d7eff',
|
|
|
|
|
|
'red',"#096","#9cf"
|
|
|
|
|
|
]
|
2025-07-25 13:28:47 +08:00
|
|
|
|
// 完全自定义菜单
|
|
|
|
|
|
this.editor.config.menus = [
|
|
|
|
|
|
'image', // 图片
|
|
|
|
|
|
'video', // 视频
|
|
|
|
|
|
'head', // 标题
|
|
|
|
|
|
'bold', // 粗体
|
|
|
|
|
|
// 'fontSize',//字号
|
|
|
|
|
|
// 'fontName',//字体
|
|
|
|
|
|
'italic', // 斜体
|
|
|
|
|
|
'underline', // 下划线
|
|
|
|
|
|
'strikeThrough', // 删除线
|
|
|
|
|
|
// 'line',//行高
|
|
|
|
|
|
'lineHeight',//
|
2025-07-28 17:01:13 +08:00
|
|
|
|
'foreColor', // 文字颜色
|
|
|
|
|
|
'backColor', // 背景颜色
|
2025-07-25 13:28:47 +08:00
|
|
|
|
// 'link', // 链接
|
|
|
|
|
|
'list', // 列表
|
|
|
|
|
|
// 'todo',//
|
|
|
|
|
|
'justify', // 对齐方式
|
|
|
|
|
|
// 'quote', // 引用
|
|
|
|
|
|
// 'emoticon',//表情
|
|
|
|
|
|
'table', // 表格
|
|
|
|
|
|
// 'code', // 代码
|
|
|
|
|
|
'splitLine',//分割线
|
|
|
|
|
|
'undo', // 撤销
|
2025-07-28 17:01:13 +08:00
|
|
|
|
'redo', // 重做
|
2025-07-25 13:28:47 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 创建编辑器
|
|
|
|
|
|
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="图片">`)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-07-28 17:01:13 +08:00
|
|
|
|
// 插入视频
|
2025-07-25 13:28:47 +08:00
|
|
|
|
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" style='display:none;'>
|
|
|
|
|
|
<span class="video-delete" data-video-id="${videoId}">× 删除</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p><br></p>
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
// 使用编辑器命令插入
|
|
|
|
|
|
this.editor.cmd.do('insertHTML', videoHtml)
|
|
|
|
|
|
|
|
|
|
|
|
// 添加删除事件监听
|
|
|
|
|
|
|
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
|
const btn = document.querySelector(`button[data-video-id="${videoId}"]`)
|
|
|
|
|
|
if (btn) {
|
|
|
|
|
|
btn.onclick = (e) => {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
this.deleteVideoById(videoId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 根据ID删除视频(修正版)
|
|
|
|
|
|
deleteVideoById(videoId) {
|
|
|
|
|
|
const container = document.querySelector(`.video-container[data-video-id="${videoId}"]`)
|
|
|
|
|
|
if (container) {
|
|
|
|
|
|
container.remove()
|
|
|
|
|
|
this.editor.txt.html(this.editor.txt.html()) // 刷新编辑器
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deleteAllVideos() {
|
|
|
|
|
|
const videoWrappers = document.querySelectorAll('.video-wrapper')
|
|
|
|
|
|
if (videoWrappers.length === 0) {
|
2025-07-28 17:01:13 +08:00
|
|
|
|
console.log('没有找到可删除的视频')
|
2025-07-25 13:28:47 +08:00
|
|
|
|
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()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-07-28 17:01:13 +08:00
|
|
|
|
// 使用正确的方式通知内容变更
|
2025-07-25 13:28:47 +08:00
|
|
|
|
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')
|
2025-07-28 17:01:13 +08:00
|
|
|
|
console.log("headings.length",headings.length)
|
2025-07-25 13:28:47 +08:00
|
|
|
|
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)
|
2025-07-28 17:01:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 生成S1000D XML模板
|
|
|
|
|
|
generateS1000DTemplate(content) {
|
|
|
|
|
|
const { dmc, issueInfo, language } = this.dmConfig;
|
|
|
|
|
|
|
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
|
<!DOCTYPE dmodule [
|
|
|
|
|
|
<!ENTITY % ISOEntities PUBLIC "ISO 8879-1986//ENTITIES ISO Character Entities 20030531//EN//XML" "http://www.s1000d.org/S1000D_4-1/ent/ISOEntities">
|
|
|
|
|
|
%ISOEntities;
|
|
|
|
|
|
]>
|
|
|
|
|
|
<dmodule 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>${dmc.modelIdentCode}-${dmc.systemDiffCode}-${dmc.systemCode}-${dmc.subSystemCode}-${dmc.subSubSystemCode}-${dmc.assyCode}-${dmc.disassyCode}-${dmc.disassyCodeVariant}</avee>
|
|
|
|
|
|
<avee>${dmc.infoCode}-${dmc.infoCodeVariant}-${dmc.itemLocationCode}</avee>
|
|
|
|
|
|
</dmc>
|
|
|
|
|
|
</dmaddress>
|
|
|
|
|
|
<issueinfo>
|
|
|
|
|
|
<issue number="${issueInfo.issueNumber}" inwork="${issueInfo.inWork}" date="${issueInfo.issueDate}"/>
|
|
|
|
|
|
<language country="${language.split('-')[1]}" language="${language.split('-')[0]}"/>
|
|
|
|
|
|
</issueinfo>
|
|
|
|
|
|
</idstatus>
|
|
|
|
|
|
<content>
|
|
|
|
|
|
<description>
|
|
|
|
|
|
${this.escapeXml(content)}
|
|
|
|
|
|
</description>
|
|
|
|
|
|
</content>
|
|
|
|
|
|
</dmodule>`;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// XML特殊字符转义
|
|
|
|
|
|
escapeXml(unsafe) {
|
|
|
|
|
|
return unsafe.replace(/[<>&'"]/g, (c) => {
|
|
|
|
|
|
switch (c) {
|
|
|
|
|
|
case '<': return '<';
|
|
|
|
|
|
case '>': return '>';
|
|
|
|
|
|
case '&': return '&';
|
|
|
|
|
|
case '\'': return ''';
|
|
|
|
|
|
case '"': return '"';
|
|
|
|
|
|
default: return c;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 更新XML预览
|
|
|
|
|
|
updateXmlPreview(html) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.xmlPreview = this.generateS1000DTemplate(html);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('生成XML预览失败:', e);
|
|
|
|
|
|
this.xmlPreview = `生成XML预览时出错: ${e.message}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 导出为S1000D XML文件
|
|
|
|
|
|
exportToS1000D() {
|
|
|
|
|
|
const html = this.editor.txt.html();
|
|
|
|
|
|
const xmlContent = this.generateS1000DTemplate(html);
|
|
|
|
|
|
|
|
|
|
|
|
// 下载文件
|
|
|
|
|
|
const blob = new Blob([xmlContent], { type: 'application/xml' });
|
|
|
|
|
|
const link = document.createElement('a');
|
|
|
|
|
|
link.href = URL.createObjectURL(blob);
|
|
|
|
|
|
link.download = `s1000d-${this.dmConfig.dmc.modelIdentCode}-${this.dmConfig.issueInfo.issueNumber}.xml`;
|
|
|
|
|
|
link.click();
|
|
|
|
|
|
URL.revokeObjectURL(link.href);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 从S1000D XML导入
|
|
|
|
|
|
handleS1000DUpload(event) {
|
|
|
|
|
|
const file = event.target.files[0];
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
reader.onload = (e) => {
|
|
|
|
|
|
const xmlString = e.target.result;
|
|
|
|
|
|
const html = this.extractContentFromS1000D(xmlString);
|
|
|
|
|
|
|
|
|
|
|
|
if (html) {
|
|
|
|
|
|
this.editor.txt.html(html);
|
|
|
|
|
|
this.updateXmlPreview(html);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置input值,允许重复选择同一文件
|
|
|
|
|
|
event.target.value = '';
|
|
|
|
|
|
};
|
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 从S1000D XML提取内容
|
|
|
|
|
|
extractContentFromS1000D(xmlString) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 简单提取description内容
|
|
|
|
|
|
const descriptionMatch = xmlString.match(/<description>([\s\S]*?)<\/description>/i);
|
|
|
|
|
|
if (!descriptionMatch || !descriptionMatch[1]) {
|
|
|
|
|
|
throw new Error('未找到description内容');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 反转义XML特殊字符
|
|
|
|
|
|
let content = descriptionMatch[1]
|
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
|
.replace(/'/g, "'")
|
|
|
|
|
|
.replace(/"/g, '"');
|
|
|
|
|
|
|
|
|
|
|
|
// 移除可能的多余空格和换行
|
|
|
|
|
|
content = content.trim();
|
|
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('S1000D导入错误:', e);
|
|
|
|
|
|
alert(`导入S1000D XML失败: ${e.message}`);
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 验证当前XML
|
|
|
|
|
|
validateCurrentXml() {
|
|
|
|
|
|
const validationResult = this.validateS1000DXml(this.xmlPreview);
|
|
|
|
|
|
this.validationResult = validationResult;
|
|
|
|
|
|
this.showValidationModal = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 基本S1000D XML验证
|
|
|
|
|
|
validateS1000DXml(xmlString) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查基本结构
|
|
|
|
|
|
const hasDmodule = xmlString.includes('<dmodule');
|
|
|
|
|
|
const hasIdstatus = xmlString.includes('<idstatus');
|
|
|
|
|
|
const hasContent = xmlString.includes('<content');
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasDmodule) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '无效的S1000D文档: 缺少dmodule根元素'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasIdstatus) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '缺少必需的S1000D元素: idstatus'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasContent) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '缺少必需的S1000D元素: content'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查dmc结构
|
|
|
|
|
|
const hasDmc = xmlString.includes('<dmc>');
|
|
|
|
|
|
const hasAvee = xmlString.includes('<avee>');
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasDmc || !hasAvee) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '无效的dmc结构: 缺少dmc或avee元素'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: true,
|
|
|
|
|
|
message: 'XML文档符合S1000D基本结构要求'
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: `验证过程中发生错误: ${e.message}`
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-07-25 13:28:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2025-07-28 17:01:13 +08:00
|
|
|
|
text-align: left;
|
2025-07-25 13:28:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
#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;
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 禁用菜单项样式 */
|
|
|
|
|
|
.disabled-menu-item {
|
|
|
|
|
|
opacity: 0.5 !important;
|
|
|
|
|
|
cursor: not-allowed !important;
|
|
|
|
|
|
pointer-events: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 如果需要工具提示也禁用 */
|
|
|
|
|
|
.w-e-toolbar .w-e-menu:nth-child(1),
|
|
|
|
|
|
.w-e-toolbar .w-e-menu:nth-child(2) {
|
|
|
|
|
|
/* background-color:#096; */
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
2025-07-28 17:01:13 +08:00
|
|
|
|
.s1000d-editor-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
/*height: 100vh;*/
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar {
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tool-btn {
|
|
|
|
|
|
padding: 8px 15px;
|
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tool-btn:hover {
|
|
|
|
|
|
background-color: #66b1ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.editor-area {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.editor {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
border: 1px solid #dcdfe6;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.xml-preview {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
border: 1px solid #dcdfe6;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
background-color: #f5f7fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.xml-preview pre {
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
font-family: Consolas, Monaco, monospace;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
width: 60%;
|
|
|
|
|
|
max-height: 80%;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.close {
|
|
|
|
|
|
float: right;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.valid {
|
|
|
|
|
|
color: #67c23a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.invalid {
|
|
|
|
|
|
color: #f56c6c;
|
|
|
|
|
|
}
|
|
|
|
|
|
/deep/ .editor .w-e-text{
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
2025-07-25 13:28:47 +08:00
|
|
|
|
</style>
|