/** * 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 }) } })