1
This commit is contained in:
200
vuetemplate2js/template-editor.js
Normal file
200
vuetemplate2js/template-editor.js
Normal file
@@ -0,0 +1,200 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user