Files
vue-template-test/vuetemplate2js/template-editor.js
2025-11-24 11:37:59 +08:00

200 lines
4.6 KiB
JavaScript
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.
import { parse, generate, baseParse } from '@vue/compiler-dom';
/**
* Vue模板编辑器类
* 支持节点的移动、交换、修改等操作
*/
export class VueTemplateEditor {
constructor(template) {
this.originalTemplate = template;
this.ast = parse(template);
}
/**
* 获取模板的AST结构
*/
getAST() {
return this.ast;
}
/**
* 根据选择器查找节点
* @param {string} selector - 选择器标签名、class、id等
* @returns {Array} 匹配的节点数组
*/
findNodes(selector) {
const results = [];
this._traverseAST(this.ast, (node) => {
if (this._matchSelector(node, selector)) {
results.push(node);
}
});
return results;
}
/**
* 移动节点到新位置
* @param {Object} node - 要移动的节点
* @param {Object} targetParent - 目标父节点
* @param {number} position - 在子节点中的位置
*/
moveNode(node, targetParent, position = -1) {
// 从原位置移除节点
this._removeNodeFromParent(node);
// 添加到新位置
if (!targetParent.children) {
targetParent.children = [];
}
if (position === -1) {
targetParent.children.push(node);
} else {
targetParent.children.splice(position, 0, node);
}
// 更新父节点引用
node.parent = targetParent;
}
/**
* 交换两个节点的位置
* @param {Object} node1 - 第一个节点
* @param {Object} node2 - 第二个节点
*/
swapNodes(node1, node2) {
if (node1.parent !== node2.parent) {
throw new Error('只能交换同一父节点下的兄弟节点');
}
const parent = node1.parent;
const index1 = parent.children.indexOf(node1);
const index2 = parent.children.indexOf(node2);
if (index1 === -1 || index2 === -1) {
throw new Error('节点不在父节点的子节点列表中');
}
// 交换位置
parent.children[index1] = node2;
parent.children[index2] = node1;
}
/**
* 修改节点属性
* @param {Object} node - 要修改的节点
* @param {Object} attributes - 新的属性对象
*/
modifyNodeAttributes(node, attributes) {
if (!node.props) {
node.props = [];
}
Object.entries(attributes).forEach(([key, value]) => {
const existingPropIndex = node.props.findIndex(prop =>
prop.name === key || (prop.arg && prop.arg.content === key)
);
if (existingPropIndex !== -1) {
// 更新现有属性
const prop = node.props[existingPropIndex];
if (prop.value) {
prop.value.content = value;
}
} else {
// 添加新属性
node.props.push({
type: 6, // 属性节点
name: key,
value: {
type: 2, // 文本节点
content: value
}
});
}
});
}
/**
* 生成修改后的模板代码
* @returns {string} 新的模板代码
*/
generateTemplate() {
const result = generate(this.ast, {
mode: 'module',
source: this.originalTemplate
});
return result.code;
}
/**
* 遍历AST树
*/
_traverseAST(node, callback) {
callback(node);
if (node.children) {
node.children.forEach(child => {
if (child.type) { // 有效的AST节点
this._traverseAST(child, callback);
}
});
}
}
/**
* 匹配选择器
*/
_matchSelector(node, selector) {
if (!node.tag) return false;
// 简单选择器匹配(可扩展)
if (selector.startsWith('.')) {
// class选择器
const className = selector.slice(1);
return node.props?.some(prop =>
prop.name === 'class' && prop.value?.content.includes(className)
);
} else if (selector.startsWith('#')) {
// id选择器
const id = selector.slice(1);
return node.props?.some(prop =>
prop.name === 'id' && prop.value?.content === id
);
} else {
// 标签名选择器
return node.tag === selector;
}
}
/**
* 从父节点移除节点
*/
_removeNodeFromParent(node) {
if (node.parent && node.parent.children) {
const index = node.parent.children.indexOf(node);
if (index !== -1) {
node.parent.children.splice(index, 1);
}
}
}
}
/**
* 工具函数创建新的Vue模板编辑器实例
*/
export function createTemplateEditor(template) {
return new VueTemplateEditor(template);
}
/**
* 工具函数:格式化模板代码
*/
export function formatTemplate(template) {
const ast = parse(template);
const result = generate(ast, {
mode: 'module',
source: template
});
return result.code;
}