处理css:
// 处理css
const dealCss = () => {
allRootColorKeys = []
// https://developer.mozilla.org/zh-CN/docs/Web/API/CSSRule
const cssRulesList = Array.from(document.styleSheets).find(sheet => sheet.title === 'code-preview-css')?.cssRules
const mergeCssData = ({ itemStyle, type, oldStyles }) => {
if (oldStyles[type]) {
Object.assign(oldStyles[type], itemStyle)
} else {
oldStyles[type] = itemStyle
}
return oldStyles
}
const getMerge = ({ oldStyles, selectorText, styleMap, style }) => {
const itemStyle = {}
Array.from(style).forEach(key => {
itemStyle[camelCase(key)] = style[key]
})
if (selectorText.includes(':')) {
const reg = /([^:]+):(.+)/
const selector = selectorText.match(reg)
// console.log(selector, 'selectorselectorselector')
return mergeCssData({ itemStyle, type: `:${selector[2]}`, oldStyles })
} else {
return mergeCssData({ itemStyle, type: 'normal', oldStyles })
}
}
// export const pageColorKey = '--common-page-color-'
const dealStyleRule = (rule) => {
const { style, styleMap, selectorText } = rule
const seletorName = selectorText.match(/((.|#)?[^:]+)/)[0]
const seletor = $(seletorName)
// console.log(seletorName, 'seletorseletorseletor')
if (!seletor[0]) return
// 处理 配色colors
if (selectorText === ':root') {
const colorsArr = style.cssText.split(';')
colorsArr.forEach((item, index) => {
const [key, val] = item.split(':')
if (key.includes('--global-site-')) return
pageColors[0].color_list.push({
color_name: `模板颜色${index}`,
color_id: `@${codePattle.rand(13)}@`,
color_value: val.trim(),
color_key: `--common-page-color-${randomId()}`,
origin_key: key.trim()
})
allRootColorKeys.push(key.trim())
})
}
const key = 'data-style'
let oldStyles = seletor.attr(key) || '{}'
oldStyles = JSON.parse(oldStyles)
const dataStyle = getMerge({ oldStyles, selectorText, styleMap, style })
seletor.attr(key, JSON.stringify(dataStyle))
}
Array.from(cssRulesList).forEach(rule => {
console.log(rule, 'rulerulerule')
if (rule.constructor === CSSStyleRule) {
dealStyleRule(rule)
} else if (rule.constructor === CSSMediaRule) {
const { conditionText } = rule
const key = 'data-media'
Array.from(rule.cssRules).forEach(item => {
// console.log(key, item)
const { style, styleMap, selectorText } = item
const seletorName = selectorText.match(/((.|#)?[^:]+)/)[0]
let oldAllMediaStyle = $(seletorName).attr(key) || '{}'
oldAllMediaStyle = JSON.parse(oldAllMediaStyle)
const oldStyles = oldAllMediaStyle[conditionText] || {}
const dataStyle = getMerge({ oldStyles, selectorText, styleMap, style })
oldAllMediaStyle[conditionText] = dataStyle
$(seletorName).attr(key, JSON.stringify(oldAllMediaStyle))
})
// const { style, styleMap, selectorText } = rule
}
})
// $(selectorText).attr()
// 'data-media': {
// 'screen and (min-width: 800px)' : {
// normal: {}
// },
// 'screen and (min-width: 600px)' : {
// normal: {}
// },
// }
}
处理js:
// 处理js
const dealJs = () => {
const placeholder = '%blockID%'
if (!newPage.javascript) return
const blockName = newPage.javascript.match(/(?<=\/\/\s+).*?(?=\s+start)/g)
console.log(blockName, 1111)
if (!blockName) return
blockName.forEach(item => {
const rex = new RegExp(`(?<=\\/\\/\\s+${item}\\s+start)[\\s\\S]*(?=\\/\\/\\s+${item}\\s+end)`, 'g')
const currFun = newPage.javascript.match(rex)?.[0]
const itemFunStr = currFun.replace(/(?<=\$\(').*?(?='\))/g, (v, a) => {
if (v.includes(item)) {
return v.replace(item, placeholder)
} else {
return `${placeholder} ${v}`
}
})
console.log(itemFunStr)
$(item).attr('data-event', itemFunStr)
})
}
一对一处理dom
const getDomAttrJson = (dom, type) => {
const domStyle = dom.getAttribute(`data-${type}`) || '{}'
return JSON.parse(domStyle) || {}
}
// 自定义当前节点填充数据
const matchingItemData = (newNodes, domTreeItem) => {
if (['text', 'paragraph', 'date', 'title', 'button'].indexOf(newNodes.code) !== -1) {
newNodes.data.text = domTreeItem.innerHTML
newNodes.data.grade = domTreeItem.tagName.toLowerCase()
}
}
// 自定义父节点数据填充
const matchingItemPaPaData = (newNodes, domTreeItem) => {
if (newNodes?.code && ['image', 'video', 'author-head'].includes(newNodes.code)) {
for (let i = 0; i < domTreeItem.childNodes.length; i++) {
if (domTreeItem.childNodes[i].src) {
newNodes.data.url = domTreeItem.childNodes[i].src
}
if (domTreeItem.childNodes[i].innerHTML) {
newNodes.data.text = domTreeItem.childNodes[i].innerHTML
}
}
}
}
// 子节点样式数据处理
const matchingItemStyle = (newNodes, domTreeItem) => {
const config = editor.components.config.getComponentConfig({ group: newNodes.group, code: newNodes.code })
newNodes.name = config.name
const childStyle = {}
const childMedia = {}
codePattle.traversal(domTreeItem, itemdom => {
const domStyle = getDomAttrJson(itemdom, 'style')
const domMedia = getDomAttrJson(itemdom, 'media')
for (const key in domStyle) {
const selector = key === 'normal' ? (itemdom.classList[0] ? `.${itemdom.classList[0]}` : itemdom.nodeName.toLowerCase()) : key
childStyle[selector] = domStyle[key]
}
itemdom.removeAttribute('data-style')
// 将子节点的样式保存起来
Object.assign(newNodes.interact, childStyle)
for (const key in domMedia) {
childMedia[key] = {}
for (const k in domMedia[key]) {
const selector = k === 'normal' ? (itemdom.classList[0] ? `.${itemdom.classList[0]}` : itemdom.nodeName.toLowerCase()) : key
childMedia[key][selector] = domMedia[key][k]
}
Object.assign(newNodes.media[key], childMedia[key])
}
itemdom.removeAttribute('data-media')
})
// 加入默认属性,防止编辑属性时缺失字段报错
console.log(newNodes.interact, 'newNodes.interactnewNodes.interact')
newNodes.interact = { ...config.defaultData.interact, ...newNodes.interact }
}
// domToJson
const domToJson = async () => {
const { widthRange, design, gutter, show, style } = editor.state.data.pages[0]
const newPages = {
pages: [{
childrenData: [],
widthRange,
design,
gutter,
show,
style
}]
}
const eachHtml = (domTree) => {
let newNodes = {}
const childrenData = []
for (let i = 0; i < domTree.length; i++) {
const currentDom = domTree[i]
if (currentDom.nodeType !== Node.TEXT_NODE && currentDom.nodeType !== Node.COMMENT_NODE) {
// 节点信息初始化
const tagName = currentDom.localName.toUpperCase()
let code = ''
if (currentDom.nodeType === Node.ELEMENT_NODE) {
const componentCode = currentDom?.getAttribute('data-component')
if (componentCode) {
code = componentCode
} else if (/H\d/.test(tagName)) {
code = 'title'
} else if (tagName === 'P') {
code = 'paragraph'
} else if (tagName === 'PICTURE') {
code = 'image'
} else if (tagName === 'BUTTON') {
code = 'button'
} else {
code = 'column-container'
}
}
console.log(code, "currentDom?.getAttribute('data-component')")
// const initColors = {}
const allStyle = getDomAttrJson(currentDom, 'style')
newNodes = {
code,
tagName,
className: currentDom.className,
childrenData: [],
data: {},
style: cloneDeep(allStyle.normal) || {}, // 存作为标记元素的样式
// keys,
group: '基本元件',
attrs: {},
media: getDomAttrJson(currentDom, 'media'), // 存媒体查询相关css
interact: {} // 存伪元素和不在元素配置的子节点中的子元素样式
}
// 以组件/区块为单位的js脚本
const script = currentDom?.getAttribute('data-event')
script && (newNodes.script = script)
console.log(newNodes.style, 'stylestylestylestyle')
// 把当前节点交互相关的css 也保存起来
delete allStyle.normal
newNodes.interact = allStyle
Array.from(currentDom.attributes).forEach(item => {
const initarrs = ['data-container-id', 'class', 'data-component', 'data-style', 'data-media', 'data-event']
!initarrs.includes(item.name) && (newNodes.attrs[item.name] = item.value)
})
if (code === 'custom' && !currentDom?.innerHTML.includes('data-component')) {
newNodes.data.text = currentDom?.innerHTML
}
// 匹配容器子长度
const containerNumKey = newNodes.code.split('-')[1] ? newNodes.code.split('-')[0] + 'Num' : null
if (containerNumKey) {
let childNodesLength = 0
let containerNum = {}
currentDom?.childNodes?.length > 0 && currentDom.childNodes.forEach(item => {
if (item.nodeType !== Node.TEXT_NODE && item.nodeType !== Node.COMMENT_NODE) {
++childNodesLength
}
})
containerNum[containerNumKey] = childNodesLength
newNodes.childNodesLength = childNodesLength
newNodes.data = containerNum
newNodes.defaultData = {
layoutNum: [1, 2, 3, 4],
layoutDirection: 'row'
}
containerNum = null
}
if (newNodes.code === 'author-box') {
Object.assign(newNodes.data, {
currAuthor: {},
name: '',
url: '',
width: '100px',
height: '100px'
})
}
// 非容器组件获取子元素样式
if (newNodes.code !== 'custom' && !newNodes.code.includes('container')) {
matchingItemStyle(newNodes, currentDom)
}
// 自定义当前节点填充数据
matchingItemData(newNodes, currentDom)
// 自定义父节点数据填充
matchingItemPaPaData(newNodes, currentDom)
childrenData.push(newNodes)
newNodes.childrenData = currentDom?.childNodes && ((code === 'custom' && !newNodes.data.text) || code.includes('container')) ? eachHtml(currentDom.childNodes, newNodes) : []
}
// newNodes = null
}
return childrenData
}
// 配色key更新
let htmlCode = document.querySelector('.code-html').innerHTML
allRootColorKeys.forEach(key => {
const colorKey = pageColors[0].color_list.find(item => item.origin_key === key).color_key
htmlCode = htmlCode.replaceAll(key, colorKey)
})
document.querySelector('.code-html').innerHTML = htmlCode
const dom = document.querySelector('.code-html').childNodes[0]?.classList?.contains('cms-visual-editor-content') ? document.querySelector('.code-html').childNodes[0].childNodes : document.querySelector('.code-html').childNodes
newPages.pages[0].childrenData.push(...eachHtml(dom))
const newPagesDataCopy = cloneDeep(newPages.pages[0].childrenData)
newPages.pages[0].childrenData = []
for (const item of newPagesDataCopy) {
const { code, group, name, ...defaultData } = item
const data = await editor.methods.createComponentData({ code, group, name, ...defaultData })
newPages.pages[0].childrenData.push(data)
}
// 设置页面配色
newPages.pages[0].style.backgroundColor = 'var(--global-site-painter-color)'
pageColors[0].color_list.length && (newPages.pages[0].colors = pageColors)
if (store.state.editor.base.mainType === 2) {
const content = editor.state.data.pages[0].childrenData.find(item => item.isContent)
console.log(content, 'contentcontentcontent')
content.childrenData = newPages.pages[0].childrenData
} else {
editor.state.data = Object.assign({}, editor.state.data, newPages)
}
console.error(editor.state.data, 'editor.state.dataeditor.state.data')
console.error(newPages)
}
onMounted(() => {
monacoEditor.value = monaco.editor.create(document.getElementById('monaco-ref'), {
theme: 'vs', // 主题 vs, hc-black, or vs-dark
value: '', // 默认显示的值
language: 'json',
folding: true, // 是否折叠
foldingHighlight: true, // 折叠等高线
foldingStrategy: 'indentation', // 折叠方式 auto | indentation
showFoldingControls: 'always', // 是否一直显示折叠 always | mouseover
disableLayerHinting: true, // 等宽优化
emptySelectionClipboard: false, // 空选择剪切板
selectionClipboard: false, // 选择剪切板
automaticLayout: true, // 自动布局
codeLens: true, // 代码镜头
scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
colorDecorators: true, // 颜色装饰器
accessibilitySupport: 'on', // 辅助功能支持 "auto" | "off" | "on"
lineNumbers: 'on', // 行号 取值: "on" | "off" | "relative" | "interval" | function
lineNumbersMinChars: 5, // 行号最小字符 number
enableSplitViewResizing: false,
wordWrap: 'on',
formatOnType: true,
readOnly: false // 是否只读 取值 true | false
})
// setValue
editor.state.base.iscode = true
// 格式化
// toRaw(monacoEditor.value).getAction('editor.action.formatDocument').run()
// toRaw(monacoEditor.value).getAction('editor.action.formatDocument')._run()
toRaw(monacoEditor.value).trigger('anything', 'editor.action.formatDocument')
toRaw(monacoEditor.value).setValue(toRaw(monacoEditor.value).getValue())
monacoEditor.value.onDidChangeModelContent(() => {
console.log('内容变化 onDidChangeModelContent')
switch (toRaw(monacoEditor.value).getModel()._languageIdentifier.language) {
case 'html':
// if (stringToDom(toRaw(monacoEditor.value).getValue()).parentNode.childNodes.length > 1) {
// ElMessage.error('追加代码必须放置cms-visual-editor-content之中')
// throw new Error('追加代码必须放置cms-visual-editor-content之中')
// }
newPage.html = toRaw(monacoEditor.value).getValue()
break
case 'css':
newPage.css = toRaw(monacoEditor.value).getValue()
break
case 'javascript':
newPage.javascript = toRaw(monacoEditor.value).getValue()
break
}
})
monacoEditor.value.onDidChangeModelLanguage(() => {
console.error('语言变化')
})
monacoEditor.value.onDidBlurEditorText(() => {
console.log('失去编辑焦点')
if (['html', 'css'].includes(toRaw(monacoEditor.value).getModel()._languageIdentifier.language)) {
// dealCss()
// domToJson()
}
})
})
const confirmModel = () => {
isImportData.value = true
nextTick(() => {
dealCss()
dealJs()
// const a = 1
// if (a) return
domToJson()
setTimeout(() => {
// getReturnContent()
editor.state.base.iscode = true
isImportData.value = false
}, 1000)
})
}
return () => (
<div class="code-box">
{isImportData.value && <div class="code-preview">
<style>{newPage.defaultCss}</style>
<style title='code-preview-css'>{newPage.css}</style>
<div vHtml={newPage.html} class="code-html"></div>
<script src="https://code.jquery.com/jquery-3.6.2.js" integrity="sha256-pkn2CUZmheSeyssYw3vMp1+xyub4m+e+QK4sQskvuo4=" crossorigin="anonymous"></script>
{/* <script>{newPage.javascript}</script> */}
</div>}
<div id="monaco-ref"></div>
<div class="code-btn-group">
<el-button-group class="ml-4">
<el-button onClick={ () => changeModel('html') }>
HTML
</el-button>
<el-button onClick={ () => changeModel('css') }>
CSS
</el-button>
<el-button onClick={ () => changeModel('javascript') }>
JS
</el-button>
{/* 测试,html 和css 要同时修改转JSON才会有效 */}
<el-button onClick={ () => changeModel('json') }>
JSON
</el-button>
<el-button onClick={ confirmModel }>
确认导入
</el-button>
</el-button-group>
</div>
</div>
)