const dayjs = require("dayjs"); const ExcelJS = require('exceljs'); const Sm4Util = require("../../utils/sm4-util"); module.exports = async (item, db, res) => { switch (item.downloadType) { case 'downloadRx': return await downloadRx(item, db, res); case 'downloadOrder': return await downloadOrder(item, db, res); case 'downloadLackMedicineReg': return await downloadLackMedicineReg(item, db, res); default: return '不支持的下载类型'; } }; const rxStatusMap = { INIT: "待审核", PASS: "审核通过", UNPASS: "审核未通过", EXPIRED: "已取消", PASS_INVALID: "已作废", UNPASS_INVALID: "已作废", INVALID: '已作废', }; const orderStatusMap = { pending: "待处理", processing: "处理中", cancelled: "已取消", completed: "已完成", finished: "已结束", }; const orderSourceMap = { ALIPAY_MINI: '支付宝小程序', MATEPAD: '门店平板' } // 流式写入大数据量 async function downloadStream(db, res, options) { try { // 关闭 useStyles 提升性能 const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res, useStyles: false }); const worksheet = workbook.addWorksheet('Sheet1'); worksheet.columns = options.columns; // 提前设置响应头 const safeFilename = options.filename.replace(/[<>:"/\\|?*]+/g, '_'); res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); res.setHeader( 'Content-Disposition', `attachment; filename=${encodeURIComponent(safeFilename)}.xlsx` ); // 加大批次 const BATCH_SIZE = 2000; let skip = 0; let hasMore = true; while (hasMore) { const batch = await db.collection(options.collection).aggregate([ ...options.aggregateList, { $skip: skip }, { $limit: BATCH_SIZE } ]).toArray(); batch.forEach(i => { worksheet.addRow(options.format(i)).commit(); }); skip += batch.length; hasMore = batch.length === BATCH_SIZE; } await worksheet.commit(); await workbook.commit(); // 不需要 res.end(),ExcelJS 会自动关闭流 } catch (e) { res.status(500).send('生成Excel文件时出错' + e.message); } } async function downloadRx(ctx, db, res) { const { startDate, endDate, startAuditDate, endAuditDate } = ctx; const hasDates = startDate && dayjs(startDate).isValid() && endDate && dayjs(endDate).isValid(); const hasAuditDate = startAuditDate && dayjs(startAuditDate).isValid() && endAuditDate && dayjs(endAuditDate).isValid(); const query = {}; if (hasDates) { query.createTime = { $gte: dayjs(startDate).startOf('day').valueOf(), $lte: dayjs(endDate).endOf('day').valueOf() }; } if (hasAuditDate) { query.auditTime = { $gte: dayjs(startAuditDate).startOf('day').valueOf(), $lte: dayjs(endAuditDate).endOf('day').valueOf() }; } if (typeof ctx.name === 'string' && ctx.name.trim() !== "") { query.name = new RegExp(ctx.name); } if (typeof ctx.doctorCode === 'string') { query.doctorCode = ctx.doctorCode.trim(); } if (typeof ctx.pharmacistNo === 'string' && ctx.pharmacistNo.trim() !== "") { query.pharmacistNo = ctx.pharmacistNo.trim(); } const columns = [ { header: '状态', key: 'status', width: 10 }, { header: '原因', key: 'reasons', width: 10 }, { header: '提交时间', key: 'createTime', width: 20 }, { header: '审核时间', key: 'auditTime', width: 20 }, { header: '医生', key: 'doctorName', width: 10 }, { header: '药师', key: 'pharmacist', width: 10 }, { header: '患者', key: 'name', width: 10 }, { header: '诊断', key: 'diagnosisList', width: 10 }, { header: '药品', key: 'drugs', width: 10 }, ]; let filename = '处方记录'; if (hasDates) { filename += `_${dayjs(startDate).format('YYYY-MM-DD')}_${dayjs(endDate).format('YYYY-MM-DD')}`; } else if (hasAuditDate) { filename += `_${dayjs(startAuditDate).format('YYYY-MM-DD')}_${dayjs(endAuditDate).format('YYYY-MM-DD')}`; } try { const total = await db.collection("diagnostic-record").countDocuments(query); if (total > 120000) { return { success: false, message: `数据量过大(${total}条数据),请分批次导出` }; } if (total === 0) { return { success: false, message: `没有符合条件的数据` }; } const options = { query, columns, filename, collection: 'diagnostic-record', aggregateList: [ { $match: query }, { $project: { _id: 0, status: 1, createTime: 1, reasons: 1, auditTime: 1, name: 1, doctorName: 1, pharmacist: 1, diagnosisList: 1, drugs: 1, orderId: 1, specification: 1 } }, { $sort: { createTime: -1 } }, ], format: i => ({ status: rxStatusMap[i.status] || i.status, reasons: Array.isArray(i.reasons) ? i.reasons.join(",") : "", createTime: i.createTime ? dayjs(i.createTime).format("YYYY-MM-DD HH:mm:ss") : "", auditTime: i.auditTime ? dayjs(i.auditTime).format("YYYY-MM-DD HH:mm:ss") : "", doctorName: i.doctorName, pharmacist: i.pharmacist, name: Sm4Util.decryptDataForSm3(i.name)|| i.name, diagnosisList: Array.isArray(i.diagnosisList) ? i.diagnosisList.map(d => d.name || '').join(",") : "", drugs: Array.isArray(i.drugs) ? i.drugs.map(d => `${d.drugName} ${i.specification || ''} (${d.dosage}${d.dosage_unit}, ${d.frequencyName}, ${d.days}天)`).join(",") : "", }) } await downloadStream(db, res, options); } catch (e) { return { success: false, message: e.message || "查询失败" }; } } async function downloadOrder(ctx, db, res) { const { startDate, endDate } = ctx; const hasDates = startDate && dayjs(startDate).isValid() && endDate && dayjs(endDate).isValid(); const query = {}; if (hasDates) { query.createTime = { $gte: dayjs(startDate).startOf('day').valueOf(), $lte: dayjs(endDate).endOf('day').valueOf() }; } if (typeof ctx.name === 'string' && ctx.name.trim() !== "") { query.name = new RegExp(ctx.name); } const doctorCodes = typeof ctx.doctorCodes === 'string' && ctx.doctorCodes.trim() ? ctx.doctorCodes.split(',').map(code => code.trim()).filter(Boolean) : []; if (doctorCodes.length) { query.doctorCode = { $in: doctorCodes }; } const storeIds = typeof ctx.storeIds === 'string' && ctx.storeIds.trim() ? ctx.storeIds.split(',').map(code => code.trim()).filter(Boolean) : []; if (storeIds.length) { query.drugStoreId = { $in: storeIds }; } if (typeof ctx.orderSource === 'string' && ctx.orderSource.trim() !== "") { query.orderSource = ctx.orderSource.trim() } if (['true', 'false'].includes(ctx.isPres)) { query.preOrderrId = { $exists: ctx.isPres === 'true' } } if (typeof ctx.orderStatus === 'string' && ctx.orderStatus.trim() !== "") { query.orderStatus = ctx.orderStatus } const columns = [ { header: '状态', key: 'status', width: 10 }, { header: '患者', key: 'name', width: 10 }, { header: '病历号', key: 'blhno', width: 10 }, { header: '医生', key: 'doctorName', width: 20 }, { header: '医生工号', key: 'doctorCode', width: 20 }, { header: '科室', key: 'deptName', width: 10 }, { header: '下单时间', key: 'orderTime', width: 10 }, { header: '问诊类型', key: 'orderType', width: 10 }, { header: '来源', key: 'drugStoreName', width: 10 }, { header: '终端', key: 'orderSource', width: 10 }, { header: '订单号', key: 'orderId', width: 10 }, ]; let filename = '咨询订单'; if (hasDates) { filename += `_${dayjs(startDate).format('YYYY-MM-DD')}_${dayjs(endDate).format('YYYY-MM-DD')}`; } try { const total = await db.collection("consult-order").countDocuments(query); if (total > 10000) { return { success: false, message: `数据量过大(${total}条数据),请分批次导出` }; } if (total === 0) { return { success: false, message: `没有符合条件的数据` }; } const options = { query, columns, filename, collection: 'consult-order', aggregateList: [ { $match: query }, { $project: { _id: 0, orderStatus: 1, name: 1, blhno: 1, createTime: 1, doctorName: 1, doctorCode: 1, deptName: 1, preOrderrId: 1, drugStoreName: 1, orderSource: 1, orderId: 1 } }, { $addFields: { orderTime: { $dateToString: { format: "%Y-%m-%d", date: { $toDate: "$createTime" }, timezone: "Asia/Shanghai" // 设置为中国标准时间 } } } }, { $sort: { createTime: -1 } }, ], format: i => ({ status: orderStatusMap[i.orderStatus] || i.orderStatus, name: i.name, blhno: i.blhno, doctorName: i.doctorName, doctorCode: i.doctorCode, deptName: i.deptName, orderTime: i.orderTime, orderType: i.preOrderId ? '在线续方' : '在线复诊', drugStoreName: i.drugStoreName, orderSource: orderSourceMap[i.orderSource] || i.orderSource, orderId: i.orderId, }) } await downloadStream(db, res, options); } catch (e) { return { success: false, message: e.message || "查询失败" }; } } // 缺货登记导出 async function downloadLackMedicineReg(ctx, db, res) { const { startTime, endTime } = ctx; const hasDates = startTime && dayjs(startTime).isValid() && endTime && dayjs(endTime).isValid(); const query = {}; if (hasDates) { query.createTime = { $gte: dayjs(startTime).startOf('day').valueOf(), $lte: dayjs(endTime).endOf('day').valueOf(), }; } if (typeof ctx.corpId === 'string' && ctx.corpId.trim() !== "") { query.corpId = ctx.corpId.trim(); } if (typeof ctx.drugName === 'string' && ctx.drugName.trim() !== "") { query.drugName = new RegExp(ctx.drugName.trim(), 'i'); } if (typeof ctx.inventory === 'string') { const inventory = ctx.inventory.trim(); if (['online', 'store'].includes(inventory)) { query.inventory = inventory; } } const columns = [ { header: '登记时间', key: 'createTime', width: 20 }, { header: '药品名称', key: 'drugName', width: 20 }, { header: '规格', key: 'spec', width: 15 }, { header: '厂家', key: 'manufacturer', width: 25 }, { header: '医保编码', key: 'insuranceCode', width: 20 }, { header: '附件数量', key: 'attachmentCount', width: 12 }, { header: '备注说明', key: 'description', width: 30 }, { header: '缺货药品库', key: 'storeName', width: 15 }, { header: '患者姓名', key: 'patientName', width: 15 }, { header: '联系电话', key: 'phone', width: 15 }, ]; let filename = '缺货登记'; if (hasDates) { filename += `_${dayjs(startTime).format('YYYY-MM-DD')}_${dayjs(endTime).format('YYYY-MM-DD')}`; } try { const total = await db.collection("lack-medicine-reg").countDocuments(query); if (total > 10000) { return { success: false, message: `数据量过大(${total}条数据),请分批次导出` }; } if (total === 0) { return { success: false, message: `没有符合条件的数据` }; } const options = { query, columns, filename, collection: 'lack-medicine-reg', aggregateList: [ { $match: query }, { $project: { _id: 0, createTime: 1, drugName: 1, spec: 1, manufacturer: 1, insuranceCode: 1, images: 1, description: 1, inventory: 1, patientName: 1, phone: 1, } }, { $sort: { createTime: -1 } }, ], format: i => ({ createTime: i.createTime ? dayjs(i.createTime).format("YYYY-MM-DD HH:mm:ss") : "", drugName: i.drugName || "", spec: i.spec || "", manufacturer: i.manufacturer || "", insuranceCode: i.insuranceCode || "", attachmentCount: Array.isArray(i.images) ? i.images.length : 0, description: i.description || "", storeName: i.inventory === 'store' ? '药店药品库' : '配送药品库', patientName: i.patientName || "", phone: i.phone || "", }) }; await downloadStream(db, res, options); } catch (e) { return { success: false, message: e.message || "查询失败" }; } }