ai牛逼
This commit is contained in:
@@ -375,3 +375,509 @@ export function getTemplateAST(vueContent) {
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入新元素到指定位置
|
||||
*
|
||||
* @param {string} vueContent - 完整的Vue文件内容
|
||||
* @param {Object} options - 插入选项
|
||||
* @param {string} options.templateContent - 要插入的模板内容
|
||||
* @param {string} options.targetPath - 目标元素路径,如 "r1c2"
|
||||
* @param {string} options.direction - 插入方向: 'top' | 'bottom' | 'left' | 'right' | 'inside'
|
||||
* @returns {Object} - { success: boolean, content?: string, error?: string }
|
||||
*/
|
||||
/**
|
||||
* 解析Vue文件的el-row/el-col结构树
|
||||
* @param {string} vueContent - Vue文件内容
|
||||
* @returns {Object} - { success: boolean, tree?: Array, error?: string }
|
||||
*/
|
||||
export function parseElementTree(vueContent) {
|
||||
try {
|
||||
// 1. 解析Vue SFC文件
|
||||
const sfcResult = parseSFC(vueContent)
|
||||
|
||||
if (sfcResult.errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Vue文件解析错误: ${sfcResult.errors[0].message}`
|
||||
}
|
||||
}
|
||||
|
||||
const templateBlock = sfcResult.descriptor.template
|
||||
if (!templateBlock) {
|
||||
return {
|
||||
success: false,
|
||||
error: '未找到template块'
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 解析template获取AST
|
||||
const templateAST = parseTemplate(templateBlock.content, {
|
||||
comments: true,
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
|
||||
// 3. 递归构建el-row/el-col树
|
||||
const buildTree = (children, pathPrefix = '') => {
|
||||
const result = []
|
||||
let rowIndex = 0
|
||||
let colIndex = 0
|
||||
|
||||
for (const child of children) {
|
||||
if (child.type !== 1) continue // 跳过非元素节点
|
||||
|
||||
if (child.tag === 'el-row') {
|
||||
rowIndex++
|
||||
const path = pathPrefix + 'r' + rowIndex
|
||||
const node = {
|
||||
type: 'row',
|
||||
path,
|
||||
label: 'el-row',
|
||||
children: buildTree(child.children || [], path)
|
||||
}
|
||||
result.push(node)
|
||||
} else if (child.tag === 'el-col') {
|
||||
colIndex++
|
||||
const path = pathPrefix + 'c' + colIndex
|
||||
|
||||
// 检查el-col内部的子组件
|
||||
const componentName = getInnerComponentName(child.children || [])
|
||||
const spanAttr = child.props?.find(p => p.name === 'span' || (p.name === 'bind' && p.arg?.content === 'span'))
|
||||
let span = 24
|
||||
if (spanAttr) {
|
||||
if (spanAttr.name === 'span' && spanAttr.value) {
|
||||
span = parseInt(spanAttr.value.content) || 24
|
||||
} else if (spanAttr.exp) {
|
||||
span = parseInt(spanAttr.exp.content) || 24
|
||||
}
|
||||
}
|
||||
|
||||
const node = {
|
||||
type: 'col',
|
||||
path,
|
||||
label: `el-col :span="${span}"`,
|
||||
componentName,
|
||||
children: buildTree(child.children || [], path)
|
||||
}
|
||||
result.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 获取el-col内部的组件名称(优先读data-component属性)
|
||||
const getInnerComponentName = (children) => {
|
||||
for (const child of children) {
|
||||
if (child.type !== 1) continue
|
||||
if (child.tag === 'el-row' || child.tag === 'el-col') continue
|
||||
|
||||
// 优先检查 data-component 属性
|
||||
if (child.props) {
|
||||
const dataComponentAttr = child.props.find(p => p.name === 'data-component')
|
||||
if (dataComponentAttr?.value?.content) {
|
||||
return dataComponentAttr.value.content
|
||||
}
|
||||
}
|
||||
|
||||
// 其他元素返回"其他"
|
||||
return '其他'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const tree = buildTree(templateAST.children)
|
||||
|
||||
return { success: true, tree }
|
||||
|
||||
} catch (error) {
|
||||
console.error('[parseElementTree] 错误:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
export function insertElement(vueContent, options) {
|
||||
const { templateContent: insertContent, targetPath, direction } = options
|
||||
|
||||
try {
|
||||
// 1. 解析Vue SFC文件
|
||||
const sfcResult = parseSFC(vueContent)
|
||||
|
||||
if (sfcResult.errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Vue文件解析错误: ${sfcResult.errors[0].message}`
|
||||
}
|
||||
}
|
||||
|
||||
const templateBlock = sfcResult.descriptor.template
|
||||
if (!templateBlock) {
|
||||
return {
|
||||
success: false,
|
||||
error: '未找到template块'
|
||||
}
|
||||
}
|
||||
|
||||
const templateContent = templateBlock.content
|
||||
|
||||
// 找到 <template> 内容在原文件中的实际位置
|
||||
const templateTagStart = vueContent.indexOf('<template')
|
||||
const templateTagEnd = vueContent.indexOf('>', templateTagStart) + 1
|
||||
const templateCloseStart = vueContent.indexOf('</template>', templateTagEnd)
|
||||
|
||||
const contentStart = templateTagEnd
|
||||
const contentEnd = templateCloseStart
|
||||
|
||||
// 2. 解析template获取AST
|
||||
const templateAST = parseTemplate(templateContent, {
|
||||
comments: true,
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
|
||||
// 3. 查找目标元素
|
||||
const targetNode = findElementByPath(templateAST, targetPath)
|
||||
|
||||
if (!targetNode) {
|
||||
return { success: false, error: `未找到目标元素: ${targetPath}` }
|
||||
}
|
||||
|
||||
// 4. 获取目标元素的位置和缩进
|
||||
const targetStart = targetNode.loc.start.offset
|
||||
const targetEnd = targetNode.loc.end.offset
|
||||
const targetLineStart = templateContent.lastIndexOf('\n', targetStart - 1) + 1
|
||||
const targetIndent = templateContent.substring(targetLineStart, targetStart)
|
||||
|
||||
console.log(`[insertElement] 目标: ${targetPath} [${targetStart}-${targetEnd}]`)
|
||||
console.log(`[insertElement] 方向: ${direction}`)
|
||||
|
||||
let newTemplateContent
|
||||
|
||||
// 5. 根据方向插入
|
||||
if (direction === 'inside') {
|
||||
// 放入目标元素内部
|
||||
const targetText = templateContent.substring(targetStart, targetEnd)
|
||||
const closeTagPattern = `</${targetNode.tag}>`
|
||||
const closeTagIndex = targetText.lastIndexOf(closeTagPattern)
|
||||
|
||||
if (closeTagIndex === -1) {
|
||||
return { success: false, error: `未找到目标元素的结束标签` }
|
||||
}
|
||||
|
||||
const insertPosition = targetStart + closeTagIndex
|
||||
const childIndent = targetIndent + ' '
|
||||
const adjustedContent = adjustIndentation(insertContent, childIndent)
|
||||
const insertText = adjustedContent + '\n' + targetIndent
|
||||
|
||||
newTemplateContent = templateContent.substring(0, insertPosition) +
|
||||
insertText +
|
||||
templateContent.substring(insertPosition)
|
||||
} else {
|
||||
// 放在目标元素前面或后面
|
||||
const insertAfterTarget = (direction === 'bottom' || direction === 'right')
|
||||
const insertPosition = insertAfterTarget ? targetEnd : targetStart
|
||||
|
||||
const adjustedContent = adjustIndentation(insertContent, targetIndent)
|
||||
|
||||
const insertText = insertAfterTarget
|
||||
? '\n' + adjustedContent
|
||||
: adjustedContent + '\n'
|
||||
|
||||
newTemplateContent = templateContent.substring(0, insertPosition) +
|
||||
insertText +
|
||||
templateContent.substring(insertPosition)
|
||||
}
|
||||
|
||||
// 6. 重建Vue文件
|
||||
const newVueContent = vueContent.substring(0, contentStart) +
|
||||
newTemplateContent +
|
||||
vueContent.substring(contentEnd)
|
||||
|
||||
console.log('[insertElement] 插入成功')
|
||||
|
||||
return { success: true, content: newVueContent }
|
||||
|
||||
} catch (error) {
|
||||
console.error('[insertElement] 错误:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析组件属性值
|
||||
* @param {string} vueContent - Vue文件内容
|
||||
* @param {string} elementPath - 元素路径
|
||||
* @returns {Object} - { success: boolean, props?: Object, componentId?: string, error?: string }
|
||||
*/
|
||||
export function parseComponentProps(vueContent, elementPath) {
|
||||
try {
|
||||
const sfcResult = parseSFC(vueContent)
|
||||
if (sfcResult.errors.length > 0) {
|
||||
return { success: false, error: `Vue文件解析错误` }
|
||||
}
|
||||
|
||||
const templateBlock = sfcResult.descriptor.template
|
||||
if (!templateBlock) {
|
||||
return { success: false, error: '未找到template块' }
|
||||
}
|
||||
|
||||
const templateAST = parseTemplate(templateBlock.content, {
|
||||
comments: true,
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
|
||||
const targetNode = findElementByPath(templateAST, elementPath)
|
||||
if (!targetNode) {
|
||||
return { success: false, error: `未找到元素: ${elementPath}` }
|
||||
}
|
||||
|
||||
// 提取属性
|
||||
const props = {}
|
||||
|
||||
// 提取span属性(从el-col)
|
||||
if (targetNode.tag === 'el-col') {
|
||||
const spanProp = targetNode.props?.find(p =>
|
||||
p.name === 'span' || (p.name === 'bind' && p.arg?.content === 'span')
|
||||
)
|
||||
if (spanProp) {
|
||||
if (spanProp.name === 'span' && spanProp.value) {
|
||||
props.span = parseInt(spanProp.value.content) || 24
|
||||
} else if (spanProp.exp) {
|
||||
props.span = parseInt(spanProp.exp.content) || 24
|
||||
}
|
||||
} else {
|
||||
props.span = 24
|
||||
}
|
||||
}
|
||||
|
||||
// 查找内部组件并提取属性
|
||||
let componentId = null
|
||||
const extractChildProps = (children) => {
|
||||
for (const child of children || []) {
|
||||
if (child.type !== 1) continue
|
||||
if (child.tag === 'el-row' || child.tag === 'el-col') continue
|
||||
|
||||
// 检查 data-component 属性
|
||||
const dataCompAttr = child.props?.find(p => p.name === 'data-component')
|
||||
if (dataCompAttr?.value?.content) {
|
||||
componentId = dataCompAttr.value.content
|
||||
}
|
||||
|
||||
// 提取子元素属性
|
||||
extractElementProps(child, props)
|
||||
|
||||
// 递归提取孩子节点
|
||||
if (child.children) {
|
||||
extractChildProps(child.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提取元素属性
|
||||
const extractElementProps = (node, result) => {
|
||||
if (!node.props) return
|
||||
|
||||
for (const prop of node.props) {
|
||||
if (prop.type === 6) { // 普通属性
|
||||
const key = `${node.tag}:${prop.name}`
|
||||
result[key] = prop.value?.content || ''
|
||||
} else if (prop.type === 7 && prop.name === 'bind') { // v-bind 属性
|
||||
const key = `${node.tag}::${prop.arg?.content}`
|
||||
result[key] = prop.exp?.content || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractChildProps(targetNode.children)
|
||||
|
||||
console.log('[parseComponentProps] 解析属性:', { elementPath, componentId, props })
|
||||
|
||||
return { success: true, props, componentId }
|
||||
|
||||
} catch (error) {
|
||||
console.error('[parseComponentProps] 错误:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组件属性值
|
||||
* @param {string} vueContent - Vue文件内容
|
||||
* @param {string} elementPath - 元素路径
|
||||
* @param {Object} updates - 要更新的属性 { key: value }
|
||||
* @returns {Object} - { success: boolean, content?: string, error?: string }
|
||||
*/
|
||||
export function updateComponentProps(vueContent, elementPath, updates) {
|
||||
try {
|
||||
const sfcResult = parseSFC(vueContent)
|
||||
if (sfcResult.errors.length > 0) {
|
||||
return { success: false, error: `Vue文件解析错误` }
|
||||
}
|
||||
|
||||
const templateBlock = sfcResult.descriptor.template
|
||||
if (!templateBlock) {
|
||||
return { success: false, error: '未找到template块' }
|
||||
}
|
||||
|
||||
const templateContent = templateBlock.content
|
||||
const templateTagStart = vueContent.indexOf('<template')
|
||||
const templateTagEnd = vueContent.indexOf('>', templateTagStart) + 1
|
||||
const templateCloseStart = vueContent.indexOf('</template>', templateTagEnd)
|
||||
|
||||
const templateAST = parseTemplate(templateContent, {
|
||||
comments: true,
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
|
||||
const targetNode = findElementByPath(templateAST, elementPath)
|
||||
if (!targetNode) {
|
||||
return { success: false, error: `未找到元素: ${elementPath}` }
|
||||
}
|
||||
|
||||
let newTemplateContent = templateContent
|
||||
|
||||
// 处理span更新(el-col的:span属性)
|
||||
if (updates.span !== undefined && targetNode.tag === 'el-col') {
|
||||
const nodeText = templateContent.substring(targetNode.loc.start.offset, targetNode.loc.end.offset)
|
||||
const spanMatch = nodeText.match(/:span="(\d+)"/)
|
||||
if (spanMatch) {
|
||||
const newNodeText = nodeText.replace(/:span="\d+"/, `:span="${updates.span}"`)
|
||||
newTemplateContent = newTemplateContent.substring(0, targetNode.loc.start.offset) +
|
||||
newNodeText +
|
||||
newTemplateContent.substring(targetNode.loc.end.offset)
|
||||
}
|
||||
delete updates.span
|
||||
}
|
||||
|
||||
// 处理其他属性更新
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
const [tagName, attrName] = key.split(':')
|
||||
if (!tagName || !attrName) continue
|
||||
|
||||
// 查找目标元素
|
||||
const findTargetElement = (node) => {
|
||||
if (node.type === 1 && node.tag === tagName) return node
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const found = findTargetElement(child)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const targetEl = findTargetElement(targetNode)
|
||||
if (!targetEl) continue
|
||||
|
||||
const elStart = targetEl.loc.start.offset
|
||||
const elEnd = targetEl.loc.end.offset
|
||||
let elText = newTemplateContent.substring(elStart, elEnd)
|
||||
|
||||
// 更新属性
|
||||
const isBinding = attrName.startsWith(':')
|
||||
const realAttrName = isBinding ? attrName.substring(1) : attrName
|
||||
const attrRegex = new RegExp(`(${isBinding ? ':' : ''}${realAttrName})="[^"]*"`)
|
||||
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
// 删除属性
|
||||
elText = elText.replace(attrRegex, '')
|
||||
elText = elText.replace(/\s+>/g, '>').replace(/\s{2,}/g, ' ')
|
||||
} else if (attrRegex.test(elText)) {
|
||||
// 更新属性
|
||||
elText = elText.replace(attrRegex, `${isBinding ? ':' : ''}${realAttrName}="${value}"`)
|
||||
} else {
|
||||
// 添加属性
|
||||
const tagEndMatch = elText.match(/<[\w-]+/)
|
||||
if (tagEndMatch) {
|
||||
const insertPos = tagEndMatch[0].length
|
||||
elText = elText.substring(0, insertPos) +
|
||||
` ${isBinding ? ':' : ''}${realAttrName}="${value}"` +
|
||||
elText.substring(insertPos)
|
||||
}
|
||||
}
|
||||
|
||||
newTemplateContent = newTemplateContent.substring(0, elStart) +
|
||||
elText +
|
||||
newTemplateContent.substring(elEnd)
|
||||
}
|
||||
|
||||
const newVueContent = vueContent.substring(0, templateTagEnd) +
|
||||
newTemplateContent +
|
||||
vueContent.substring(templateCloseStart)
|
||||
|
||||
console.log('[updateComponentProps] 更新成功')
|
||||
|
||||
return { success: true, content: newVueContent }
|
||||
|
||||
} catch (error) {
|
||||
console.error('[updateComponentProps] 错误:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除元素
|
||||
* @param {string} vueContent - Vue文件内容
|
||||
* @param {string} elementPath - 元素路径
|
||||
* @returns {Object} - { success: boolean, content?: string, error?: string }
|
||||
*/
|
||||
export function deleteElement(vueContent, elementPath) {
|
||||
try {
|
||||
const sfcResult = parseSFC(vueContent)
|
||||
if (sfcResult.errors.length > 0) {
|
||||
return { success: false, error: `Vue文件解析错误` }
|
||||
}
|
||||
|
||||
const templateBlock = sfcResult.descriptor.template
|
||||
if (!templateBlock) {
|
||||
return { success: false, error: '未找到template块' }
|
||||
}
|
||||
|
||||
const templateContent = templateBlock.content
|
||||
const templateTagStart = vueContent.indexOf('<template')
|
||||
const templateTagEnd = vueContent.indexOf('>', templateTagStart) + 1
|
||||
const templateCloseStart = vueContent.indexOf('</template>', templateTagEnd)
|
||||
|
||||
const templateAST = parseTemplate(templateContent, {
|
||||
comments: true,
|
||||
whitespace: 'preserve'
|
||||
})
|
||||
|
||||
const targetNode = findElementByPath(templateAST, elementPath)
|
||||
if (!targetNode) {
|
||||
return { success: false, error: `未找到元素: ${elementPath}` }
|
||||
}
|
||||
|
||||
// 计算删除范围
|
||||
let deleteStart = targetNode.loc.start.offset
|
||||
let deleteEnd = targetNode.loc.end.offset
|
||||
|
||||
// 向前查找这一行的开始
|
||||
const lineStart = templateContent.lastIndexOf('\n', deleteStart - 1) + 1
|
||||
const beforeElement = templateContent.substring(lineStart, deleteStart)
|
||||
|
||||
// 如果元素前面只有空白,则从行开始删除
|
||||
if (/^\s*$/.test(beforeElement)) {
|
||||
deleteStart = lineStart
|
||||
}
|
||||
|
||||
// 向后查找是否有换行
|
||||
if (templateContent[deleteEnd] === '\n') {
|
||||
deleteEnd = deleteEnd + 1
|
||||
}
|
||||
|
||||
const newTemplateContent = templateContent.substring(0, deleteStart) +
|
||||
templateContent.substring(deleteEnd)
|
||||
|
||||
const newVueContent = vueContent.substring(0, templateTagEnd) +
|
||||
newTemplateContent +
|
||||
vueContent.substring(templateCloseStart)
|
||||
|
||||
console.log(`[deleteElement] 删除成功: ${elementPath}`)
|
||||
|
||||
return { success: true, content: newVueContent }
|
||||
|
||||
} catch (error) {
|
||||
console.error('[deleteElement] 错误:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user