Files
fauto-design/vue-template-service/src/index.js
2026-01-20 21:53:09 +08:00

409 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 })
}
})