杨若瑜更新3.9版本
57f2e961创建于 2024年3月31日历史提交
let cssCache = {}
let cssParentCache = {}
let jsCache = {}
let imgTagName="img"

function genCssContent(block, unitRate, unitName, parentBlock) {
	let css = '';
	for (let key in block.style) {
		let value = new String(block.style[key])
		// 处理RPX换算问题
		if (value) {
			let allCells = value.split(' ')
			for (let i = 0; i < allCells.length; i++) {
				if (allCells[i].match('[0-9]+px')) {
					allCells[i] = (parseInt(allCells[i].replace('px', '')) * unitRate) + unitName
				}
			}
			value = allCells.join(' ')
		}
		let realCss = key.replace(/([A-Z])/g, "-$1").toLowerCase()
		// 过滤一些不需要的
		if (realCss == 'font-weight' && value == 'normal') continue
		if (realCss == 'font-style' && value == 'normal') continue
		// 过滤无子元素的样式
		if (!(block.blocks && (block.blocks.length > 0 || block.text != null))) {
			if (realCss == 'display' && value == 'flex') continue
			if (realCss == 'position' && value == 'relative') continue
			if (realCss == 'width' && value == '100%' && block.domType == 'div' && parentBlock != null &&
				parentBlock.blocks.length > 1) continue
			if (realCss == 'flex-direction') continue
			if (realCss == 'justify-content') continue
			if (realCss == 'align-items') continue
		}
		// 过滤Flex布局的默认值
		if (realCss == 'flex-direction' && value == 'row') continue
		if (realCss == 'justify-content' && value == 'flex-start') continue
		if (realCss == 'align-items' && value == 'flex-start') continue
		// 没有边框和padding就无需加上box-sizing
		if (!(block.style.border ||
			block.style.borderBottom ||
			block.style.borderLeft ||
			block.style.borderRight ||
			block.style.borderTop ||
			block.style.padding ||
			block.style.paddingTop ||
			block.style.paddingLeft ||
			block.style.paddingRight ||
			block.style.paddingBottom)) {
			if (realCss == 'box-sizing' && value == 'border-box') continue
		}
		if (realCss == 'border-top' && value == 'none') continue
		if (realCss == 'border-left' && value == 'none') continue
		if (realCss == 'border-right' && value == 'none') continue
		if (realCss == 'border-bottom' && value == 'none') continue
		if (realCss == 'border') {
			if (
				block.style.borderBottom == 'none' ||
				block.style.borderLeft == 'none' ||
				block.style.borderRight == 'none' ||
				block.style.borderTop == 'none'
			) {
				continue
			}
		}
		if (value.indexOf('null') != -1) continue

		// 添加到结果中
		css += `\t\t${realCss}:${value};\n`
	}
	return css;
}

function genCssMapWithChildren(block, unitRate, unitName, parentBlock, xpath) {
	let result = xpath + "\n" + genCssContent(block, unitRate, unitName, parentBlock)
	for (let i in block.blocks) {
		result += `${xpath}>${i}\n`;
		result += genCssContent(block.blocks[i], unitRate, unitName, block, xpath + ">" + i)
	}
	return result
}

function handleCss(block, unitRate, unitName, parentBlock, css, cssName, parentName, parentCssPath, result) {
	// 样式
	css = genCssContent(block, unitRate, unitName, parentBlock);

	let cssMap = ''
	// css合并缓存
	if (css != '') {
		// 递归获取包括当前CSS和所有子CSS,构筑成Key
		cssMap = genCssMapWithChildren(block, unitRate, unitName, parentBlock, "self")
		if (cssCache[cssMap]&&cssParentCache[cssCache[cssMap]]&&cssParentCache[cssCache[cssMap]] == parentCssPath) {
			block.id = cssCache[cssMap]
			css = ''
		}
	}

	// 处理cssName
	cssName = block.id
	let currentCssPath = `.${block.id}`
	if (block.id.startsWith(parentName)) {
		cssName = block.id.substr(parentName.length + 1)
		currentCssPath = `${parentCssPath}>.${cssName}`
	}

	// 完成CSS的添加
	if (css != '') {
		result.css += `\t${currentCssPath}{\n` + css + `\t}`;
		if ((block.id.endsWith('-button') || block.id.endsWith('-btn'))) {
			result.css += `\n\t${currentCssPath}{\n\t\tuser-select: none;\n\t}`;
			result.css += `\n\t${currentCssPath}:hover{\n\t\topacity: 0.8;\n\t}`;
			result.css += `\n\t${currentCssPath}:active{\n\t\topacity: 0.5;\n\t}`;
		}
	}

	// 将CSS缓存起来,用来合并CSS
	if (css != '' && cssMap != '') {
		cssParentCache[block.id] = parentCssPath
		cssCache[cssMap] = block.id
	}
	return {css, cssName, currentCssPath};
}

/**
 * 转换为vue代码
 *    {
 *        // BLOCK对象
 *        block,
 *        defaultTag: 'view',
 *        stylesUnit: {
 *            name: 'rpx',
 *            rate: 2
 *        },
 *        clickEvent: 'tap'
 *    }
 */
let parse = (config) => {
	// 读取生成参数
	let {
		block,
		level,
		defaultTag,
		parentName,
		parentCssPath,
		clickEvent,
		parentBlock
	} = config;
	if (!config.stylesUnit) {
		config.stylesUnit = {
			name: 'px',
			rate: 1
		}
	}
	let unitName = config.stylesUnit.name
	let unitRate = config.stylesUnit.rate
	if (!level) {
		level = 1
	}

	if (level == 1) {
		cssCache = {}
		cssParentCache = {}
		jsCache = {}
		// 深拷贝
		block = JSON.parse(JSON.stringify(block))
	}
	let result = {
		html: '',
		css: '',
		js: ''
	}
	let html = ''

	let js = ''

	let {css, cssName, currentCssPath} =
		handleCss(block, unitRate, unitName, parentBlock, '', '', parentName, parentCssPath, result);
	/* 事件处理部分 */

	// 获取驼峰式Id
	let camelCaseId = block.id.toLowerCase().replace(/-(\w)/g, (match, p1) => p1.toUpperCase())

	// 处理按钮,先看CSS,如果CSS为空,那一定是不需要加事件
	if (css != '') {
		if ((block.id.endsWith('-button') || block.id.endsWith('-btn'))) {
			result.js +=
				`\tfunction ${camelCaseId}Click(){\n\t\talert("已触发${camelCaseId}Click事件")\n\t}`
		}
	}

	// 合并相似的事件
	if (result.js) {
		jsCache[block.id] = result.js
	}
	if (jsCache[block.id]) {
		js = jsCache[block.id]
	}

	/* DOM组件生成 */

	// 处理组件类型转换
	let targetDomType = block.domType
	if (block.domType) {
		if (block.domType == 'div') {
			targetDomType = defaultTag
		} else if (block.domType == 'img') {
			targetDomType = imgTagName
		} else {
			targetDomType = block.domType
		}
	} else {
		targetDomType = defaultTag
	}

	// 处理缩进
	for (let i = 0; i < level; i++) html += '\t'
	// 标签开始
	html +=
		`<${targetDomType} class="${cssName}"`
	if (js) html += ` onclick="${camelCaseId}Click()"`
	if (block.domType && block.domType == 'input' && block.text) html += ` value="${block.text}"`
	if (block.domType == 'input' || block.domType == 'img' || block.domType == imgTagName) {
		html += '/>'
	} else {
		html += '>'
	}
	// 内容
	if (block.text && (!block.domType || block.domType != 'input')) {
		if (block.blocks && block.blocks.length >= 1) {
			html += '\n'
			for (let i = 0; i < level + 1; i++) {
				html += '\t'
			}
		}
		html += block.text
	}
	if (block.blocks && block.blocks.length >= 1) {
		html += '\n'
		for (let i = 0; i < block.blocks.length; i++) {
			let child = block.blocks[i]
			let childHtml = parse({
				...config,
				block: child,
				level: level + 1,
				parentName: block.id,
				parentCssPath: currentCssPath,
				parentBlock: block,
			})
			html += childHtml.html
			// 顺带处理CSS
			if (childHtml.css != '') {
				result.css += '\n' + childHtml.css
			}
			if (childHtml.js != '') {
				if (result.js == '') {
					result.js = childHtml.js
				} else {
					result.js += '\n' + childHtml.js
				}
			}
			if (i != block.blocks.length - 1) {
				html += '\n'
			}
		}
		html += '\n'
		for (let i = 0; i < level; i++) {
			html += '\t'
		}
	}

	if (!(block.domType == 'input' || block.domType == 'img' || block.domType == imgTagName)) {
		html += `</${targetDomType}>`
	}

	// 尾部
	if (block.id == 'root') {
		let outputFile = ""
		outputFile += "<!DOCTYPE html>\n" + "<html lang=\"zhCN\">\n"
            + "<head>\n"
            +"\t<meta charset=\"UTF-8\">\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n"
            + "\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
            + "\t<title>Document</title>\n"
            + "</head>\n"
            + "<body>\n"
		outputFile += `${html}\n`
		outputFile += "\t<script>\n"
		outputFile += result.js
		outputFile += "\n\t</" + "script>\n\n"
		outputFile += "\t<style>\n"
		outputFile += `\thtml,body,.root{\n`
		outputFile += `\t\tpadding:0;\n`
		outputFile += `\t\tmargin:0;\n`
		outputFile += `\t\twidth:100%;\n`
		outputFile += `\t\theight:100%;\n`
		outputFile += `\t}\n`
		outputFile += `\tinput{\n`
		outputFile += `\t\toutline:none;\n`
		outputFile += `\t\tborder:none;\n`
		outputFile += `\t}\n`
		outputFile += result.css + "\n"
		outputFile += "\t</style>\n"
		outputFile += "</body>\n"
		outputFile += "</html>"
		html = outputFile
	}
	result.html = html
	return result
}
export default parse