初步完成

This commit is contained in:
wfz
2026-01-20 20:25:03 +08:00
parent 1bf26e6e71
commit 4a90340ab3
10 changed files with 763 additions and 1715 deletions

View File

@@ -100,6 +100,43 @@ function getNodeSourceText(source, node) {
return source.substring(start, end)
}
/**
* 获取字符串的缩进级别(空格数)
*/
function getIndentLevel(str) {
const match = str.match(/^(\s*)/)
return match ? match[1].length : 0
}
/**
* 调整源代码的缩进,保持相对缩进关系
* @param {string} sourceText - 源元素文本
* @param {string} targetIndent - 目标位置的缩进
* @returns {string} - 调整后的文本
*/
function adjustIndentation(sourceText, targetIndent) {
const lines = sourceText.split('\n')
if (lines.length === 0) return sourceText
// 获取第一行的原始缩进
const firstLineIndent = getIndentLevel(lines[0])
// 计算缩进差值
const targetIndentLevel = targetIndent.length
const indentDiff = targetIndentLevel - firstLineIndent
// 对每一行应用缩进差值
const adjustedLines = lines.map((line, index) => {
if (!line.trim()) return '' // 空行保持空
const currentIndent = getIndentLevel(line)
const newIndent = Math.max(0, currentIndent + indentDiff)
return ' '.repeat(newIndent) + line.trimStart()
})
return adjustedLines.join('\n')
}
/**
* 将元素移动到目标元素内部
* 如果目标已有子元素,则放到最后
@@ -109,81 +146,62 @@ function moveElementInside(templateContent, sourceNode, targetNode, sourcePath,
const sourceEnd = sourceNode.loc.end.offset
const sourceText = templateContent.substring(sourceStart, sourceEnd)
// 找到源元素行的开始位置(用于删除整行)
const sourceLineStart = templateContent.lastIndexOf('\n', sourceStart - 1) + 1
// 1. 计算删除范围
let deleteStart = sourceStart
let deleteEnd = sourceEnd
// 获取目标元素的缩进(子元素需要比父元素多一层缩进)
// 向前查找这一行的开始
const lineStart = templateContent.lastIndexOf('\n', sourceStart - 1) + 1
const beforeElement = templateContent.substring(lineStart, sourceStart)
// 如果元素前面只有空白,则从行开始删除
if (/^\s*$/.test(beforeElement)) {
deleteStart = lineStart
}
// 向后查找是否有换行
if (templateContent[sourceEnd] === '\n') {
deleteEnd = sourceEnd + 1
}
// 2. 获取目标元素的缩进(子元素需要比父元素多一层缩进)
const targetLineStart = templateContent.lastIndexOf('\n', targetNode.loc.start.offset - 1) + 1
const targetIndent = templateContent.substring(targetLineStart, targetNode.loc.start.offset)
const childIndent = targetIndent + ' ' // 子元素缩进
const childIndent = targetIndent + ' '
// 找到目标元素的结束标签位置
// 目标元素内容的结束位置(结束标签前)
// 3. 找到目标元素的结束标签位置
const targetText = templateContent.substring(targetNode.loc.start.offset, targetNode.loc.end.offset)
const targetTag = targetNode.tag // 'el-row' 或 'el-col'
const closeTagPattern = `</${targetTag}>`
const closeTagPattern = `</${targetNode.tag}>`
const closeTagIndex = targetText.lastIndexOf(closeTagPattern)
if (closeTagIndex === -1) {
console.error('[moveElementInside] 未找到结束标签:', targetTag)
console.error('[moveElementInside] 未找到结束标签:', targetNode.tag)
return templateContent
}
// 计算插入位置(在结束标签之前)
const insertPositionInTarget = closeTagIndex
const insertPositionInTemplate = targetNode.loc.start.offset + insertPositionInTarget
// 插入位置(在结束标签之前)
const insertPosition = targetNode.loc.start.offset + closeTagIndex
// 调整源元素的缩进
const sourceLines = sourceText.split('\n')
const adjustedSourceLines = sourceLines.map((line, index) => {
if (index === 0) {
return childIndent + line.trimStart()
}
// 保持相对缩进
return childIndent + line.trimStart()
})
const adjustedSourceText = adjustedSourceLines.join('\n')
// 4. 调整源元素的缩进(保持相对缩进)
const adjustedSourceText = adjustIndentation(sourceText, childIndent)
// 根据源和目标的位置关系进行操作
if (sourceStart < targetNode.loc.start.offset) {
// 源在目标前面:先删除源,再插入
// 删除源元素
const beforeSource = templateContent.substring(0, sourceLineStart)
const afterSource = templateContent.substring(sourceEnd)
let afterSourceTrimmed = afterSource.startsWith('\n') ? afterSource.substring(1) : afterSource
const withoutSource = beforeSource + afterSourceTrimmed
// 计算新的插入位置
const removedLength = templateContent.length - withoutSource.length
const newInsertPosition = insertPositionInTemplate - removedLength
// 插入到目标内部
const insertText = adjustedSourceText + '\n' + targetIndent
return withoutSource.substring(0, newInsertPosition) +
insertText +
withoutSource.substring(newInsertPosition)
// 构建插入文本
const insertText = adjustedSourceText + '\n' + targetIndent
// 5. 执行操作(从后向前处理)
if (deleteStart > insertPosition) {
// 删除位置在插入位置后面:先删除,再插入
let result = templateContent.substring(0, deleteStart) + templateContent.substring(deleteEnd)
result = result.substring(0, insertPosition) + insertText + result.substring(insertPosition)
return result
} else {
// 源在目标后面:先插入,再删除
// 删除位置在插入位置前面:先插入,再删除
const deletedLength = deleteEnd - deleteStart
const adjustedInsertPos = insertPosition - deletedLength
// 插入到目标内部
const insertText = adjustedSourceText + '\n' + targetIndent
const withInsert = templateContent.substring(0, insertPositionInTemplate) +
insertText +
templateContent.substring(insertPositionInTemplate)
// 计算源元素新位置
const insertedLength = insertText.length
const newSourceLineStart = sourceLineStart + insertedLength
const newSourceEnd = sourceEnd + insertedLength
// 删除源元素
const beforeNewSource = withInsert.substring(0, newSourceLineStart)
const afterNewSource = withInsert.substring(newSourceEnd)
let afterTrimmed = afterNewSource.startsWith('\n') ? afterNewSource.substring(1) : afterNewSource
return beforeNewSource + afterTrimmed
let result = templateContent.substring(0, deleteStart) + templateContent.substring(deleteEnd)
result = result.substring(0, adjustedInsertPos) + insertText + result.substring(adjustedInsertPos)
return result
}
}
@@ -222,21 +240,16 @@ export function moveElement(vueContent, options) {
const templateContent = templateBlock.content
// 找到 <template> 内容在原文件中的实际位置
// templateBlock.loc 指向整个 <template>...</template> 块
// 我们需要找到内容的起始和结束位置
const templateTagStart = vueContent.indexOf('<template')
const templateTagEnd = vueContent.indexOf('>', templateTagStart) + 1
const templateCloseStart = vueContent.indexOf('</template>', templateTagEnd)
// template内容在原文件中的位置
const contentStart = templateTagEnd
const contentEnd = templateCloseStart
// 2. 解析template获取AST
const templateAST = parseTemplate(templateContent, {
// 保留注释
comments: true,
// 保留空白(用于保持格式)
whitespace: 'preserve'
})
@@ -245,20 +258,14 @@ export function moveElement(vueContent, options) {
const targetNode = findElementByPath(templateAST, targetPath)
if (!sourceNode) {
return {
success: false,
error: `未找到源元素: ${sourcePath}`
}
return { success: false, error: `未找到源元素: ${sourcePath}` }
}
if (!targetNode) {
return {
success: false,
error: `未找到目标元素: ${targetPath}`
}
return { success: false, error: `未找到目标元素: ${targetPath}` }
}
// 4. 获取元素在template中的位置
// 4. 获取元素在template中的精确位置
const sourceStart = sourceNode.loc.start.offset
const sourceEnd = sourceNode.loc.end.offset
const sourceText = templateContent.substring(sourceStart, sourceEnd)
@@ -270,18 +277,32 @@ export function moveElement(vueContent, options) {
console.log(`[moveElement] 目标: ${targetPath} [${targetStart}-${targetEnd}]`)
console.log(`[moveElement] 方向: ${direction}`)
// 5. 计算插入位置和新内容
let newTemplateContent
// 5. 计算删除范围(包括前面的缩进和后面的换行)
// 找到源元素所在行的开始
let deleteStart = sourceStart
let deleteEnd = sourceEnd
// 检测源元素前面的空白(用于保持缩进
const sourceLineStart = templateContent.lastIndexOf('\n', sourceStart - 1) + 1
const sourceIndent = templateContent.substring(sourceLineStart, sourceStart)
// 向前查找这一行的开始(换行符后的第一个字符
const lineStart = templateContent.lastIndexOf('\n', sourceStart - 1) + 1
const beforeElement = templateContent.substring(lineStart, sourceStart)
// 检测目标元素的缩进
// 如果元素前面只有空白,则从行开始删除
if (/^\s*$/.test(beforeElement)) {
deleteStart = lineStart
}
// 向后查找是否有换行
if (templateContent[sourceEnd] === '\n') {
deleteEnd = sourceEnd + 1
}
// 获取目标元素的缩进
const targetLineStart = templateContent.lastIndexOf('\n', targetStart - 1) + 1
const targetIndent = templateContent.substring(targetLineStart, targetStart)
// 处理 'inside' 方向(放入目标元素内部)
let newTemplateContent
// 6. 处理 'inside' 方向
if (direction === 'inside') {
newTemplateContent = moveElementInside(
templateContent,
@@ -290,94 +311,51 @@ export function moveElement(vueContent, options) {
sourcePath,
targetPath
)
} else if (sourceStart < targetStart) {
// 源在目标前面:先处理目标位置,再删除源
const insertPosition = (direction === 'bottom' || direction === 'right')
? targetEnd
: targetStart
// 构建新内容
let parts = []
// 删除源元素(包括前后的空白)
const beforeSource = templateContent.substring(0, sourceLineStart)
const afterSource = templateContent.substring(sourceEnd)
// 合并时跳过源元素后的换行
let afterSourceTrimmed = afterSource
if (afterSource.startsWith('\n')) {
afterSourceTrimmed = afterSource.substring(1)
}
const withoutSource = beforeSource + afterSourceTrimmed
// 计算新的目标位置(因为删除了源,位置会变化)
const removedLength = templateContent.length - withoutSource.length
const newInsertPosition = insertPosition - removedLength
// 在新位置插入源元素
const insertText = (direction === 'bottom' || direction === 'right')
? '\n' + targetIndent + sourceText
: sourceText + '\n' + targetIndent
newTemplateContent =
withoutSource.substring(0, newInsertPosition) +
insertText +
withoutSource.substring(newInsertPosition)
} else {
// 源在目标后面:先插入,再删除
const insertPosition = (direction === 'bottom' || direction === 'right')
? targetEnd
: targetStart
// 7. 计算插入位置
const insertAfterTarget = (direction === 'bottom' || direction === 'right')
const insertPosition = insertAfterTarget ? targetEnd : targetStart
// 先在目标位置插入
const insertText = (direction === 'bottom' || direction === 'right')
? '\n' + targetIndent + sourceText
: sourceText + '\n' + targetIndent
// 构建插入文本(使用目标缩进,保持相对缩进)
const adjustedSource = adjustIndentation(sourceText, targetIndent)
const withInsert =
templateContent.substring(0, insertPosition) +
insertText +
templateContent.substring(insertPosition)
const insertText = insertAfterTarget
? '\n' + adjustedSource
: adjustedSource + '\n'
// 计算源元素新位置(因为插入了内容,位置会变化
const insertedLength = insertText.length
const newSourceStart = sourceLineStart + insertedLength
const newSourceEnd = sourceEnd + insertedLength
// 删除源元素
const beforeNewSource = withInsert.substring(0, newSourceStart)
const afterNewSource = withInsert.substring(newSourceEnd)
// 跳过换行
let afterTrimmed = afterNewSource
if (afterTrimmed.startsWith('\n')) {
afterTrimmed = afterTrimmed.substring(1)
// 8. 执行操作(从后向前处理,避免偏移量问题
if (deleteStart > insertPosition) {
// 删除位置在插入位置后面:先删除,再插入
newTemplateContent = templateContent.substring(0, deleteStart) +
templateContent.substring(deleteEnd)
newTemplateContent = newTemplateContent.substring(0, insertPosition) +
insertText +
newTemplateContent.substring(insertPosition)
} else {
// 删除位置在插入位置前面:先插入,再删除(需要调整偏移)
const deletedLength = deleteEnd - deleteStart
const adjustedInsertPos = insertPosition - deletedLength
newTemplateContent = templateContent.substring(0, deleteStart) +
templateContent.substring(deleteEnd)
newTemplateContent = newTemplateContent.substring(0, adjustedInsertPos) +
insertText +
newTemplateContent.substring(adjustedInsertPos)
}
newTemplateContent = beforeNewSource + afterTrimmed
}
// 6. 重建Vue文件:保留 <template> 标签,只替换内容
const beforeContent = vueContent.substring(0, contentStart)
const afterContent = vueContent.substring(contentEnd)
const newVueContent = beforeContent + newTemplateContent + afterContent
// 9. 重建Vue文件
const newVueContent = vueContent.substring(0, contentStart) +
newTemplateContent +
vueContent.substring(contentEnd)
console.log('[moveElement] 文件更新成功')
return {
success: true,
content: newVueContent
}
return { success: true, content: newVueContent }
} catch (error) {
console.error('[moveElement] 错误:', error)
return {
success: false,
error: error.message
}
return { success: false, error: error.message }
}
}