409 lines
10 KiB
JavaScript
409 lines
10 KiB
JavaScript
/**
|
||
* Vue模板源码修改服务
|
||
*
|
||
* 接收前端拖放请求,修改Vue文件中的template结构
|
||
*/
|
||
|
||
import express from 'express'
|
||
import cors from 'cors'
|
||
import { moveElement, insertElement, parseElementTree, parseComponentProps, updateComponentProps, deleteElement } from './services/templateService.js'
|
||
import { readFile, writeFile } from 'fs/promises'
|
||
import path from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||
|
||
const app = express()
|
||
const PORT = 3001
|
||
|
||
// 中间件
|
||
app.use(cors())
|
||
app.use(express.json())
|
||
|
||
// Vue源码目录(相对于服务根目录)
|
||
const VUE_SOURCE_DIR = path.resolve('../draggable-panels/src/views')
|
||
|
||
// 设计组件模板目录
|
||
const DESIGN_COMPONENTS_DIR = path.resolve('../draggable-panels/src/fauto/designComponents')
|
||
|
||
/**
|
||
* API: 执行元素移动操作
|
||
*
|
||
* POST /api/move-element
|
||
* Body: {
|
||
* pagePath: string, // Vue文件路径(相对于views目录)
|
||
* source: { type, path, elementType },
|
||
* targetPath: string,
|
||
* targetType: string,
|
||
* direction: 'top' | 'bottom' | 'left' | 'right'
|
||
* }
|
||
*/
|
||
app.post('/api/move-element', async (req, res) => {
|
||
try {
|
||
const { pagePath, source, targetPath, targetType, direction } = req.body
|
||
|
||
console.log('[API] 收到移动请求:', {
|
||
pagePath,
|
||
source: source.path,
|
||
target: targetPath,
|
||
direction
|
||
})
|
||
|
||
// 验证参数
|
||
if (!pagePath || !source?.path || !targetPath || !direction) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少必要参数'
|
||
})
|
||
}
|
||
|
||
// 构建完整文件路径
|
||
// 前端传来的路径可能是多种格式:
|
||
// - "../../../views/TestPage1.vue"
|
||
// - "../views/xxx.vue"
|
||
// - "./views/xxx.vue"
|
||
// - "TestPage1.vue"
|
||
// 需要提取 views/ 后面的部分
|
||
let normalizedPath = pagePath
|
||
|
||
// 查找 views/ 的位置,取其后面的内容
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6) // 6 = 'views/'.length
|
||
}
|
||
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
console.log('[API] 原始路径:', pagePath)
|
||
console.log('[API] 规范化路径:', normalizedPath)
|
||
console.log('[API] 完整路径:', filePath)
|
||
|
||
// 读取Vue文件
|
||
let vueContent
|
||
try {
|
||
vueContent = await readFile(filePath, 'utf-8')
|
||
} catch (err) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
error: `文件不存在: ${pagePath}`
|
||
})
|
||
}
|
||
|
||
// 执行移动操作
|
||
const result = moveElement(vueContent, {
|
||
sourcePath: source.path,
|
||
targetPath,
|
||
direction
|
||
})
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: result.error
|
||
})
|
||
}
|
||
|
||
// 写回文件
|
||
await writeFile(filePath, result.content, 'utf-8')
|
||
|
||
console.log('[API] 移动成功')
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `已将 ${source.path} 移动到 ${targetPath} 的${direction}方向`
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('[API] 错误:', error)
|
||
res.status(500).json({
|
||
success: false,
|
||
error: error.message
|
||
})
|
||
}
|
||
})
|
||
|
||
/**
|
||
* API: 获取服务状态
|
||
*/
|
||
app.get('/api/health', (req, res) => {
|
||
res.json({
|
||
status: 'ok',
|
||
service: 'vue-template-service',
|
||
sourceDir: VUE_SOURCE_DIR
|
||
})
|
||
})
|
||
|
||
/**
|
||
* API: 插入设计组件
|
||
*
|
||
* POST /api/insert-component
|
||
* Body: {
|
||
* pagePath: string, // Vue文件路径
|
||
* componentId: string, // 设计组件ID
|
||
* targetPath: string, // 目标元素路径
|
||
* direction: string // 插入方向
|
||
* }
|
||
*/
|
||
app.post('/api/insert-component', async (req, res) => {
|
||
try {
|
||
const { pagePath, componentId, targetPath, direction } = req.body
|
||
|
||
console.log('[API] 收到插入请求:', {
|
||
pagePath,
|
||
componentId,
|
||
targetPath,
|
||
direction
|
||
})
|
||
|
||
// 验证参数
|
||
if (!pagePath || !componentId || !targetPath || !direction) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少必要参数'
|
||
})
|
||
}
|
||
|
||
// 构建完整文件路径
|
||
let normalizedPath = pagePath
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6)
|
||
}
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
// 读取设计组件模板
|
||
const templatePath = path.join(DESIGN_COMPONENTS_DIR, componentId, 'template.html')
|
||
let componentTemplate
|
||
try {
|
||
componentTemplate = await readFile(templatePath, 'utf-8')
|
||
} catch (err) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
error: `设计组件模板不存在: ${componentId}`
|
||
})
|
||
}
|
||
|
||
console.log('[API] 设计组件模板:', componentTemplate.substring(0, 100) + '...')
|
||
|
||
// 读取Vue文件
|
||
let vueContent
|
||
try {
|
||
vueContent = await readFile(filePath, 'utf-8')
|
||
} catch (err) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
error: `文件不存在: ${pagePath}`
|
||
})
|
||
}
|
||
|
||
// 执行插入操作
|
||
const result = insertElement(vueContent, {
|
||
templateContent: componentTemplate.trim(),
|
||
targetPath,
|
||
direction
|
||
})
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: result.error
|
||
})
|
||
}
|
||
|
||
// 写回文件
|
||
await writeFile(filePath, result.content, 'utf-8')
|
||
|
||
console.log('[API] 插入成功')
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `已将 ${componentId} 插入到 ${targetPath} 的${direction}方向`
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('[API] 错误:', error)
|
||
res.status(500).json({
|
||
success: false,
|
||
error: error.message
|
||
})
|
||
}
|
||
})
|
||
|
||
/**
|
||
* API: 获取页面元素结构树
|
||
*
|
||
* GET /api/element-tree?pagePath=xxx
|
||
*/
|
||
app.get('/api/element-tree', async (req, res) => {
|
||
try {
|
||
const { pagePath } = req.query
|
||
|
||
if (!pagePath) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少pagePath参数'
|
||
})
|
||
}
|
||
|
||
// 构建完整文件路径
|
||
let normalizedPath = pagePath
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6)
|
||
}
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
console.log('[API] 获取结构树:', filePath)
|
||
|
||
// 读取Vue文件
|
||
let vueContent
|
||
try {
|
||
vueContent = await readFile(filePath, 'utf-8')
|
||
} catch (err) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
error: `文件不存在: ${pagePath}`
|
||
})
|
||
}
|
||
|
||
// 解析结构树
|
||
const result = parseElementTree(vueContent)
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: result.error
|
||
})
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
tree: result.tree
|
||
})
|
||
|
||
} catch (error) {
|
||
console.error('[API] 错误:', error)
|
||
res.status(500).json({
|
||
success: false,
|
||
error: error.message
|
||
})
|
||
}
|
||
})
|
||
|
||
// 启动服务
|
||
app.listen(PORT, () => {
|
||
console.log(`🚀 Vue模板服务启动: http://localhost:${PORT}`)
|
||
console.log(`📁 源码目录: ${VUE_SOURCE_DIR}`)
|
||
})
|
||
|
||
/**
|
||
* API: 获取组件属性
|
||
* GET /api/component-props?pagePath=xxx&elementPath=xxx
|
||
*/
|
||
app.get('/api/component-props', async (req, res) => {
|
||
try {
|
||
const { pagePath, elementPath } = req.query
|
||
|
||
if (!pagePath || !elementPath) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少参数'
|
||
})
|
||
}
|
||
|
||
let normalizedPath = pagePath
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6)
|
||
}
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
const vueContent = await readFile(filePath, 'utf-8')
|
||
const result = parseComponentProps(vueContent, elementPath)
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json(result)
|
||
}
|
||
|
||
res.json(result)
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message })
|
||
}
|
||
})
|
||
|
||
/**
|
||
* API: 更新组件属性
|
||
* POST /api/update-props
|
||
*/
|
||
app.post('/api/update-props', async (req, res) => {
|
||
try {
|
||
const { pagePath, elementPath, updates } = req.body
|
||
|
||
if (!pagePath || !elementPath || !updates) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少参数'
|
||
})
|
||
}
|
||
|
||
let normalizedPath = pagePath
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6)
|
||
}
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
const vueContent = await readFile(filePath, 'utf-8')
|
||
const result = updateComponentProps(vueContent, elementPath, updates)
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json(result)
|
||
}
|
||
|
||
await writeFile(filePath, result.content, 'utf-8')
|
||
|
||
res.json({ success: true, message: '属性更新成功' })
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message })
|
||
}
|
||
})
|
||
|
||
/**
|
||
* API: 删除元素
|
||
* POST /api/delete-element
|
||
*/
|
||
app.post('/api/delete-element', async (req, res) => {
|
||
try {
|
||
const { pagePath, elementPath } = req.body
|
||
|
||
if (!pagePath || !elementPath) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
error: '缺少参数'
|
||
})
|
||
}
|
||
|
||
let normalizedPath = pagePath
|
||
const viewsIndex = normalizedPath.indexOf('views/')
|
||
if (viewsIndex !== -1) {
|
||
normalizedPath = normalizedPath.substring(viewsIndex + 6)
|
||
}
|
||
const filePath = path.join(VUE_SOURCE_DIR, normalizedPath)
|
||
|
||
const vueContent = await readFile(filePath, 'utf-8')
|
||
const result = deleteElement(vueContent, elementPath)
|
||
|
||
if (!result.success) {
|
||
return res.status(400).json(result)
|
||
}
|
||
|
||
await writeFile(filePath, result.content, 'utf-8')
|
||
|
||
console.log(`[API] 删除成功: ${elementPath}`)
|
||
|
||
res.json({ success: true, message: '元素删除成功' })
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message })
|
||
}
|
||
})
|