import { Router, Response, NextFunction } from 'express' import { authMiddleware, AuthRequest } from '../middleware/auth' import prisma from '../lib/prisma' const router = Router() // 提取变量名 function extractVariables(content: string): string[] { const matches = content.match(/\{\{(\w+)\}\}/g) || [] return [...new Set(matches.map(m => m.replace(/\{\{|\}\}/g, '')))] } // 列表 router.get('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { category, search } = req.query const page = parseInt(req.query.page as string) || 1 const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 200) const where: any = { orgId: req.user!.orgId, status: 'ACTIVE' } if (category) where.category = category if (search) where.name = { contains: String(search) } const [items, total] = await Promise.all([ (prisma as any).enterpriseTemplate.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (page - 1) * pageSize, take: pageSize, }), (prisma as any).enterpriseTemplate.count({ where }), ]) res.json({ success: true, data: { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) } }) } catch (err) { next(err) } }) // 新建 router.post('/', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const { name, category, description, content } = req.body if (!name || !category || !content) { return res.status(400).json({ success: false, error: { code: 'MISSING_FIELDS', message: '名称、分类、内容为必填' } }) } const variables = extractVariables(content) const template = await (prisma as any).enterpriseTemplate.create({ data: { orgId: req.user!.orgId, name, category, description: description || null, content, variables, createdBy: req.user!.id, }, }) res.json({ success: true, data: template }) } catch (err) { next(err) } }) // 详情 router.get('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const template = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) if (!template) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '模板不存在' } }) } res.json({ success: true, data: template }) } catch (err) { next(err) } }) // 更新 router.put('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const existing = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) if (!existing) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '模板不存在' } }) } const { name, category, description, content, status } = req.body const variables = content ? extractVariables(content) : existing.variables const updated = await (prisma as any).enterpriseTemplate.update({ where: { id: req.params.id }, data: { ...(name !== undefined && { name }), ...(category !== undefined && { category }), ...(description !== undefined && { description }), ...(content !== undefined && { content, variables }), ...(status !== undefined && { status }), }, }) res.json({ success: true, data: updated }) } catch (err) { next(err) } }) // 删除 router.delete('/:id', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const existing = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) if (!existing) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '模板不存在' } }) } await (prisma as any).enterpriseTemplate.delete({ where: { id: req.params.id } }) res.json({ success: true, data: { message: '已删除' } }) } catch (err) { next(err) } }) // 渲染 router.post('/:id/render', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const template = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) if (!template) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '模板不存在' } }) } const { variables } = req.body as { variables: Record } let content = template.content for (const [key, value] of Object.entries(variables || {})) { content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value) } res.json({ success: true, data: { content } }) } catch (err) { next(err) } }) // 下载 Word router.get('/:id/download', authMiddleware, async (req: AuthRequest, res: Response, next: NextFunction) => { try { const template = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId }, }) if (!template) { return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '模板不存在' } }) } // 支持通过 query 参数传入变量(如 ?name=张三&idCardNumber=xxx) let content = template.content const variables: Record = {} for (const [key, value] of Object.entries(req.query)) { if (typeof value === 'string' && key !== 'token') variables[key] = value } if (Object.keys(variables).length > 0) { for (const [key, value] of Object.entries(variables)) { content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value) } } // 将纯文本转换为HTML段落,使Word样式生效 const textToHtml = (text: string): string => { // 如果内容已包含HTML标签,直接返回 if (/<[a-z][\s\S]*>/i.test(text)) return text const lines = text.split(/\n/) let html = '' for (const line of lines) { const trimmed = line.trim() if (!trimmed) { html += '

 

' continue } if (/^第[一二三四五六七八九十百]+条/.test(trimmed)) { html += `

${trimmed}

` } else if (/^劳动合同书$|^协议书$|^通知书$|^解除劳动合同协议书$/.test(trimmed)) { html += `

${trimmed}

` } else if (/^(甲方|乙方)((盖章|签字))/.test(trimmed) || /^日期[::]/.test(trimmed)) { html += `

${trimmed}

` } else { html += `

${trimmed}

` } } return html } const htmlContent = ` ${template.name} ${textToHtml(content)}` const encoded = encodeURIComponent(template.name + '.doc') res.setHeader('Content-Type', 'application/msword; charset=utf-8') res.setHeader('Content-Disposition', `attachment; filename="${encoded}"; filename*=UTF-8''${encoded}`) res.send(htmlContent) } catch (err) { next(err) } }) export default router