'use strict'; const { TEMP_POSITION_BASE } = require('./constants'); const { hasOwn, toInt, ensureObjectPayload } = require('./helpers'); const { ensureChapter, ensureBlock, ensureInstance } = require('./repository'); const { normalizeBlockConfigForStorage } = require('./dataSourceQuery'); const { resolveBlockPayload } = require('./blockPayloadResolver'); const { reorderBlockSiblingsWithInsert, reorderBlockSiblings, } = require('./ordering'); const resolveInstanceBlockStoragePayload = async ( ctx, instanceId, blockType, config, contentSnapshot, transaction ) => { const normalizedBlockType = String(blockType || 'text').trim().toLowerCase(); const instance = await ensureInstance(ctx, instanceId, transaction); const dataSourceId = toInt(instance.dataSourceId); if (!dataSourceId && ['chart', 'table', 'kpi'].includes(normalizedBlockType)) { throw '报表实例未绑定数据源'; } const normalizedConfig = ['chart', 'table'].includes(normalizedBlockType) ? await normalizeBlockConfigForStorage( ctx, instanceId, normalizedBlockType, config || {}, transaction ) : ensureObjectPayload(config || {}, 'config'); return resolveBlockPayload(ctx, { blockType: normalizedBlockType, dataSourceId, config: normalizedConfig, contentSnapshot: contentSnapshot || null, transaction, }); }; module.exports.createBlock = async (ctx, next) => { const transaction = await ctx.app.fs.dc.orm.transaction(); try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); const chapterId = toInt(ctx.params.chapterId); if (!instanceId || !chapterId) throw '缺少参数'; await ensureChapter(ctx, instanceId, chapterId, transaction); const { blockType, position, config, contentSnapshot } = ctx.request.body || {}; const normalizedBlockType = String(blockType || 'text').trim().toLowerCase(); const storagePayload = await resolveInstanceBlockStoragePayload( ctx, instanceId, normalizedBlockType, config || {}, contentSnapshot || null, transaction ); const created = await models.ReportBlock.create({ instanceId, chapterId, blockType: normalizedBlockType, position: TEMP_POSITION_BASE - 1, config: storagePayload.config, contentSnapshot: storagePayload.contentSnapshot, updatedAt: new Date(), }, { transaction, returning: true, }); await reorderBlockSiblingsWithInsert(ctx, chapterId, created.id, position, transaction); const fresh = await models.ReportBlock.findByPk(created.id, { transaction }); await transaction.commit(); ctx.body = fresh; ctx.status = 200; } catch (error) { await transaction.rollback(); ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '创建内容块失败' }; } }; module.exports.updateBlock = async (ctx, next) => { const transaction = await ctx.app.fs.dc.orm.transaction(); try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); const blockId = toInt(ctx.params.blockId); if (!instanceId || !blockId) throw '缺少参数'; const block = await ensureBlock(ctx, instanceId, blockId, transaction); const body = ctx.request.body || {}; const updateData = {}; if (hasOwn(body, 'blockType')) { updateData.blockType = String(body.blockType || 'text').trim().toLowerCase(); } updateData.updatedAt = new Date(); if (hasOwn(body, 'config') || hasOwn(body, 'blockType')) { const finalBlockType = hasOwn(body, 'blockType') ? updateData.blockType : block.blockType; const finalConfig = hasOwn(body, 'config') ? (body.config || {}) : (block.config || {}); const finalContentSnapshot = hasOwn(body, 'contentSnapshot') ? body.contentSnapshot : block.contentSnapshot; const storagePayload = await resolveInstanceBlockStoragePayload( ctx, instanceId, finalBlockType, finalConfig, finalContentSnapshot, transaction ); updateData.config = storagePayload.config; updateData.contentSnapshot = storagePayload.contentSnapshot; } else if (hasOwn(body, 'contentSnapshot')) { updateData.contentSnapshot = body.contentSnapshot || null; } const oldChapterId = block.chapterId; const nextChapterId = hasOwn(body, 'chapterId') ? toInt(body.chapterId) : block.chapterId; if (!nextChapterId) throw '参数错误: chapterId'; await ensureChapter(ctx, instanceId, nextChapterId, transaction); const needMove = hasOwn(body, 'chapterId') || hasOwn(body, 'position'); if (!needMove) { await models.ReportBlock.update(updateData, { where: { id: blockId }, transaction }); await transaction.commit(); ctx.status = 204; return; } updateData.chapterId = nextChapterId; updateData.position = TEMP_POSITION_BASE - 1; await models.ReportBlock.update(updateData, { where: { id: blockId }, transaction }); if (oldChapterId !== nextChapterId) { await reorderBlockSiblings(ctx, oldChapterId, transaction); } const targetPosition = hasOwn(body, 'position') ? body.position : (oldChapterId === nextChapterId ? block.position : undefined); await reorderBlockSiblingsWithInsert(ctx, nextChapterId, blockId, targetPosition, transaction); await transaction.commit(); ctx.status = 204; } catch (error) { await transaction.rollback(); ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '更新内容块失败' }; } }; module.exports.deleteBlock = async (ctx, next) => { const transaction = await ctx.app.fs.dc.orm.transaction(); try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); const blockId = toInt(ctx.params.blockId); if (!instanceId || !blockId) throw '缺少参数'; const block = await ensureBlock(ctx, instanceId, blockId, transaction); await models.ReportBlock.destroy({ where: { id: blockId, instanceId }, transaction, }); await reorderBlockSiblings(ctx, block.chapterId, transaction); await transaction.commit(); ctx.status = 204; } catch (error) { await transaction.rollback(); ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '删除内容块失败' }; } };