| @@ -74,6 +74,7 @@ module.exports = [ | |||||
| ['/api/mp/messageDetail', 'mp/messageDetail'], | ['/api/mp/messageDetail', 'mp/messageDetail'], | ||||
| ['/api/mp/unreadCount', 'mp/unreadCount'], | ['/api/mp/unreadCount', 'mp/unreadCount'], | ||||
| ['/api/mp/subscribeConfig', 'mp/subscribeConfig'], | ['/api/mp/subscribeConfig', 'mp/subscribeConfig'], | ||||
| ['/api/mp/regenerateSign', 'mp/regenerateSign'], | |||||
| // 瘤种管理 | // 瘤种管理 | ||||
| ['/admin/tag', 'admin/tag/index'], | ['/admin/tag', 'admin/tag/index'], | ||||
| @@ -631,6 +631,56 @@ module.exports = class extends Base { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 批量重新生成声明与承诺签署图并更新数据库 | |||||
| * GET /api/mp/regenerateSign | |||||
| */ | |||||
| async regenerateSignAction() { | |||||
| return false; | |||||
| try { | |||||
| const patients = await this.model('patient') | |||||
| .field('id, name, sign_promise') | |||||
| .where({ is_deleted: 0, sign_promise: ['!=', ''] }) | |||||
| .select(); | |||||
| if (!patients.length) return this.json({ code: 1, msg: '没有需要处理的患者' }); | |||||
| const doc = await this.model('content').getByKey('sign_promise'); | |||||
| if (think.isEmpty(doc)) return this.json({ code: 1, msg: '协议内容未配置' }); | |||||
| const screenshotService = this.service('screenshot'); | |||||
| const results = []; | |||||
| let successCount = 0; | |||||
| let failCount = 0; | |||||
| for (const patient of patients) { | |||||
| try { | |||||
| const newUrl = await screenshotService.regenerate({ | |||||
| originalImageUrl: patient.sign_promise, | |||||
| title: doc.title, | |||||
| content: doc.content | |||||
| }); | |||||
| await this.model('patient').where({ id: patient.id }).update({ sign_promise: newUrl }); | |||||
| successCount++; | |||||
| results.push({ id: patient.id, name: patient.name, status: 'ok' }); | |||||
| } catch (e) { | |||||
| failCount++; | |||||
| results.push({ id: patient.id, name: patient.name, status: 'fail', error: e.message }); | |||||
| think.logger.error(`regenerateSign patient ${patient.id} error:`, e); | |||||
| } | |||||
| } | |||||
| return this.json({ | |||||
| code: 0, | |||||
| data: { total: patients.length, success: successCount, fail: failCount, results }, | |||||
| msg: `处理完成:成功${successCount},失败${failCount}` | |||||
| }); | |||||
| } catch (error) { | |||||
| think.logger.error('regenerateSign error:', error); | |||||
| return this.json({ code: 1, msg: '执行失败: ' + error.message }); | |||||
| } | |||||
| } | |||||
| // @private | // @private | ||||
| async _verifyIdCard(name, idCard) { | async _verifyIdCard(name, idCard) { | ||||
| const faceidConfig = require('../config/faceid.js'); | const faceidConfig = require('../config/faceid.js'); | ||||
| @@ -155,6 +155,99 @@ module.exports = class extends think.Service { | |||||
| </html>`; | </html>`; | ||||
| } | } | ||||
| /** | |||||
| * 重新生成签署图(保留原图签名区域,替换上方内容) | |||||
| * 原理:原图做背景 bottom 对齐,白色 overlay 覆盖上方内容区, | |||||
| * placeholder 撑出签名区高度让背景图底部签名露出 | |||||
| * @param {Object} params | |||||
| * @param {string} params.originalImageUrl - 原合成图URL | |||||
| * @param {string} params.title - 新协议标题 | |||||
| * @param {string} params.content - 新协议富文本内容 | |||||
| * @returns {string} 新合成图COS URL | |||||
| */ | |||||
| async regenerate({ originalImageUrl, title, content }) { | |||||
| const puppeteer = require('puppeteer'); | |||||
| const html = `<!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <meta charset="utf-8"> | |||||
| <style> | |||||
| * { margin: 0; padding: 0; box-sizing: border-box; } | |||||
| body { | |||||
| width: 750px; | |||||
| overflow: hidden; | |||||
| font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; | |||||
| background-image: url('${originalImageUrl}'); | |||||
| background-size: 100% auto; | |||||
| background-repeat: no-repeat; | |||||
| background-position: bottom center; | |||||
| position: relative; | |||||
| } | |||||
| .overlay { | |||||
| width: 100%; | |||||
| background: #fff; | |||||
| padding: 60px; | |||||
| } | |||||
| .title { | |||||
| text-align: center; | |||||
| font-size: 36px; | |||||
| font-weight: bold; | |||||
| margin-bottom: 40px; | |||||
| color: #222; | |||||
| } | |||||
| .content { | |||||
| font-size: 28px; | |||||
| line-height: 2; | |||||
| color: #444; | |||||
| } | |||||
| .content p { | |||||
| margin-bottom: 16px; | |||||
| text-indent: 2em; | |||||
| } | |||||
| .sign-area-placeholder { | |||||
| height: 360px; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <div class="overlay"> | |||||
| <div class="title">${title}</div> | |||||
| <div class="content">${content}</div> | |||||
| </div> | |||||
| <div class="sign-area-placeholder"></div> | |||||
| </body> | |||||
| </html>`; | |||||
| let browser; | |||||
| try { | |||||
| browser = await puppeteer.launch({ | |||||
| headless: 'new', | |||||
| args: [ | |||||
| '--no-sandbox', | |||||
| '--disable-setuid-sandbox', | |||||
| '--disable-gpu', | |||||
| '--disable-dev-shm-usage', | |||||
| '--font-render-hinting=none' | |||||
| ] | |||||
| }); | |||||
| const page = await browser.newPage(); | |||||
| await page.setViewport({ width: 750, height: 1000, deviceScaleFactor: 2 }); | |||||
| await page.setContent(html, { waitUntil: 'networkidle0', timeout: 15000 }); | |||||
| const screenshot = await page.screenshot({ fullPage: true, type: 'png' }); | |||||
| await browser.close(); | |||||
| browser = null; | |||||
| const url = await this._uploadToCos(screenshot); | |||||
| return url; | |||||
| } catch (error) { | |||||
| if (browser) await browser.close().catch(() => {}); | |||||
| throw error; | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * 上传截图到 COS | * 上传截图到 COS | ||||
| */ | */ | ||||