<script lang="ts" setup>
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, toRefs, unref, watch } from 'vue';
|
import { useRoute } from 'vue-router';
|
|
import { useGlobSetting, useMessage } from '@jnpf/hooks';
|
import { BasicForm, useForm } from '@jnpf/ui/form';
|
import { useModal } from '@jnpf/ui/modal';
|
import { JnpfUniver, JnpfUniverCellEchart, JnpfUniverFloatEchartVM } from '@jnpf/univer';
|
import { cloneDeep, downloadByUrlReport } from '@jnpf/utils';
|
|
import { usePreferences } from '@vben/preferences';
|
|
import { exportFileExcel, getPreviewTemplate } from '#/api/onlineDev/report';
|
import { ReportPrint } from '#/components/Report';
|
import { getFloatUrl } from '#/components/Report/src/helper';
|
import { useReport } from '#/components/Report/src/hooks/useReport';
|
import { $t } from '#/locales';
|
|
defineOptions({ name: 'DynamicReport' });
|
|
const { createConfirm } = useMessage();
|
|
interface State {
|
id: string;
|
reportData: any;
|
pageLoading: boolean;
|
jnpfUniverAPI: any;
|
allowExport: number;
|
allowPrint: number;
|
sheetId: string;
|
jnpfUniverKey: number;
|
queryList: any[];
|
jnpfUniverStore: any;
|
echartsImageCache: any;
|
watermarkCache: any;
|
isFromApp: boolean;
|
}
|
|
const jnpfUniverRef = ref();
|
const jnpfUniverFloatEchartVMRef = ref();
|
const jnpfUniverCellEchartRef = ref();
|
const state = reactive<State>({
|
id: '',
|
reportData: {},
|
pageLoading: false,
|
jnpfUniverAPI: null,
|
allowExport: 0,
|
allowPrint: 0,
|
sheetId: '',
|
jnpfUniverKey: 0,
|
queryList: [],
|
jnpfUniverStore: null,
|
echartsImageCache: {},
|
watermarkCache: {},
|
isFromApp: false,
|
});
|
const { pageLoading, allowExport, allowPrint, jnpfUniverKey } = toRefs(state);
|
const { isDark } = usePreferences();
|
const [registerReportPrint, { openModal: openPrintModal }] = useModal();
|
const [registerSearchForm, { updateSchema, submit: searchFormSubmit, getFieldsValue }] = useForm({
|
baseColProps: { span: 6 },
|
showActionButtonGroup: true,
|
showAdvancedButton: true,
|
compact: true,
|
});
|
const formApi = { updateSchema, getFieldsValue };
|
const { getRealEchart, getSearchSchema, searchSchemas, clearSearchSchema } = useReport(formApi);
|
const globSetting = useGlobSetting();
|
const getHasBtn = computed(() => !state.isFromApp && (state.allowExport || state.allowPrint));
|
|
watch(
|
() => isDark.value,
|
(val) => {
|
state.jnpfUniverAPI?.toggleDarkMode(val);
|
},
|
);
|
|
// 初始化
|
function init() {
|
const route = useRoute();
|
state.isFromApp = route?.query?.from === 'app';
|
state.id = state.isFromApp ? (route?.query?.id as string) : (route?.meta?.relationId as string);
|
if (!state.id) return;
|
// state.pageLoading = true;
|
state.jnpfUniverStore = null;
|
state.echartsImageCache = {};
|
state.watermarkCache = {};
|
initData();
|
}
|
function initData(data = {}) {
|
// state.pageLoading = true;
|
getPreviewTemplate(state.id, data).then((res) => {
|
state.reportData = res.data;
|
state.queryList = res.data.queryList ? JSON.parse(res.data.queryList) : [];
|
|
const snapshot = res.data.snapshot ? JSON.parse(res.data.snapshot) : null;
|
const cells = res.data.cells ? JSON.parse(res.data.cells) : {};
|
const chartData = res.data.chartData ? JSON.parse(res.data.chartData) : [];
|
|
const floatEcharts = getRealEchart(cells.floatEcharts ?? null, chartData);
|
|
const cellEcharts = getRealEchart(cells.cellEcharts ?? null, chartData);
|
const cellEchartsArray = cellEchartsObjectToArray(cellEcharts ?? {});
|
|
let floatImages = cells.floatImages ?? {};
|
const item = getFloatUrl(snapshot.resources, globSetting.reportApiURL, floatImages);
|
snapshot.resources = item.list;
|
floatImages = item.floatImages ?? {};
|
|
state.watermarkCache = {
|
show: res.data?.allowWatermark === 1,
|
config: { ...res.data?.watermarkConfig },
|
};
|
|
state.allowExport = res.data.allowExport || 0;
|
state.allowPrint = res.data.allowPrint || 0;
|
const watermarkConfig = res.data?.watermarkConfig ? JSON.parse(res.data.watermarkConfig) : {};
|
if (watermarkConfig?.showTime) {
|
watermarkConfig.content = `${watermarkConfig.content} ${getDateTimeTextByType(watermarkConfig.timeFormat)}`;
|
}
|
state.watermarkCache = {
|
show: res.data?.allowWatermark === 1,
|
config: watermarkConfig,
|
};
|
|
// clearSearchSchema();
|
nextTick(() => {
|
getSearchSchema(state.queryList, state.sheetId || '');
|
});
|
state.jnpfUniverKey = Date.now();
|
|
nextTick(() => {
|
const updateSheets = cloneDeep(snapshot?.sheets ?? {});
|
|
// 单元格图表转单元格图片
|
cellEchartsArray.forEach((cellEchartOption) => {
|
const { option: echartOption, width: echartWidth, height: echartHeight, sheetKey, rowKey, colKey, drawingId } = cellEchartOption;
|
const echartImg = unref(jnpfUniverCellEchartRef).render(echartOption, echartWidth, echartHeight);
|
try {
|
const targetDrawing = updateSheets?.[sheetKey]?.cellData?.[rowKey]?.[colKey]?.p?.drawings?.[drawingId];
|
updateSheets[sheetKey].cellData[rowKey][colKey].p.drawings[drawingId] = {
|
...targetDrawing,
|
source: echartImg,
|
};
|
} catch {
|
// console.log('渲染单元格图表失败');
|
}
|
});
|
|
// 将单元格图表的图片更新到快照中
|
snapshot.sheets = cloneDeep(updateSheets);
|
handleCreate(snapshot, floatEcharts, floatImages);
|
});
|
});
|
}
|
// 创建报表实例
|
function handleCreate(snapshot, floatEcharts, floatImages) {
|
const res = unref(jnpfUniverRef)?.handleCreateDesignUnit({
|
mode: 'preview',
|
snapshot,
|
floatEcharts,
|
floatImages,
|
watermark: state.watermarkCache,
|
uiHeader: false,
|
uiContextMenu: false,
|
readonly: true,
|
defaultActiveSheetId: state.sheetId || '',
|
loading: true,
|
darkMode: isDark.value,
|
});
|
state.jnpfUniverAPI = res ? res?.jnpfUniverAPI : null;
|
// 创建悬浮图表的图片
|
createFloatEchartsImage(floatEcharts);
|
|
onReportCommandExecuted();
|
state.jnpfUniverAPI?.getHooks()?.onSteady(() => {
|
// state.pageLoading = false;
|
});
|
}
|
function onReportCommandExecuted() {
|
state.jnpfUniverAPI?.onCommandExecuted((command: any) => {
|
const { id: commandId } = command ?? {};
|
// 切换sheet
|
if (commandId === 'sheet.operation.set-worksheet-active') {
|
if (state.sheetId != command.params.subUnitId && state.queryList?.length) {
|
nextTick(() => {
|
clearSearchSchema();
|
|
initData({ sheetId: command.params.subUnitId });
|
});
|
}
|
state.sheetId = command.params.subUnitId;
|
getSearchSchema(state.queryList, state.sheetId);
|
}
|
});
|
}
|
// 获取单元格图表的数组
|
function cellEchartsObjectToArray(cellEcharts: any) {
|
const res: any[] = [];
|
|
for (const key in cellEcharts) {
|
const { sheet, row, col, drawingId, height, width, option } = cellEcharts[key];
|
|
res.push({
|
sheetKey: sheet,
|
rowKey: row,
|
colKey: col,
|
drawingId,
|
width,
|
height,
|
option,
|
});
|
}
|
|
return res;
|
}
|
// 创建悬浮图表的图片信息
|
function createFloatEchartsImage(echarts: any) {
|
state.jnpfUniverStore = unref(jnpfUniverRef)?.getDynamicStore();
|
if (!state.jnpfUniverStore) {
|
return;
|
}
|
|
const caches = { ...unref(state.jnpfUniverStore.floatEchartToUniverImgDataCaches) };
|
|
for (const sheetKey in echarts) {
|
const target = echarts[sheetKey];
|
|
const { domId, option, width, height } = target;
|
|
const res = unref(jnpfUniverFloatEchartVMRef).render(domId, option, width, height);
|
caches[res.domId] = res?.source ?? {};
|
|
Object.assign(state.echartsImageCache, caches); // 直接修改原对象,保持响应性
|
}
|
}
|
// 获取水印时间文本
|
function getDateTimeTextByType(type: string) {
|
let currentTime = new Date(Date.now() + 8 * 3600 * 1000).toJSON().slice(0, 19).replace('T', ' ');
|
|
switch (type) {
|
case 'yyyy': {
|
currentTime = currentTime.slice(0, 4);
|
break;
|
}
|
case 'yyyy-MM': {
|
currentTime = currentTime.slice(0, 7);
|
break;
|
}
|
case 'yyyy-MM-dd': {
|
currentTime = currentTime.slice(0, 10);
|
break;
|
}
|
case 'yyyy-MM-dd HH:mm': {
|
currentTime = currentTime.slice(0, 16);
|
break;
|
}
|
default: {
|
break;
|
}
|
}
|
return currentTime;
|
}
|
// 销毁示例
|
function handleDisposeUnit() {
|
unref(jnpfUniverRef)?.handleDisposeUnit();
|
}
|
function handleSearchSubmit(data) {
|
initData({ sheetId: state.sheetId, ...data });
|
}
|
// 打印
|
function handlePrint() {
|
state.jnpfUniverStore?.setFloatEchartToUniverImgDataCaches(state.echartsImageCache);
|
const { snapshot } = unref(jnpfUniverRef)?.getPreviewWorkbookData() || {};
|
|
const sheetId = state.jnpfUniverAPI.getActiveWorkbook()?.getActiveSheet()?.getSheetId();
|
const sheetCellData = snapshot.sheets[sheetId].cellData ?? {};
|
|
let sheetCellImageNumber = 0;
|
for (const rowKey in sheetCellData) {
|
const row = sheetCellData[rowKey];
|
|
for (const colKey in row) {
|
const cellData = row[colKey] ?? {};
|
const p = cellData?.p;
|
|
if (p && Object.keys(p).length > 0 && p.constructor === Object) {
|
const orders = p?.drawingsOrder ?? [];
|
|
orders.forEach((orderKey: string) => {
|
if (p.drawings?.[orderKey]?.imageSourceType === 'BASE64') {
|
sheetCellImageNumber++;
|
}
|
});
|
}
|
}
|
}
|
|
if (sheetCellImageNumber > 100) {
|
createConfirm({
|
iconType: 'warning',
|
title: $t('common.tipTitle'),
|
content: '数据过大,请导出后使用其他工具打印',
|
okText: $t('common.exportText'),
|
onOk: () => {
|
handleDownload();
|
},
|
});
|
return;
|
}
|
|
openPrintModal(true, { id: state.reportData.versionId, fullName: state.reportData.fullName, sheetId, snapshot, watermark: state.watermarkCache });
|
}
|
// 导出
|
function handleDownload(sheetId?: string) {
|
state.jnpfUniverStore?.setFloatEchartToUniverImgDataCaches(state.echartsImageCache);
|
const { snapshot } = unref(jnpfUniverRef)?.getPreviewWorkbookData() || {};
|
|
const targetSheets = sheetId || snapshot.sheetOrder.join(','); // 多个工作本导出sheetId以逗号连接
|
const data = unref(searchSchemas).length ? getFieldsValue() : {};
|
const query = { ...data, sheetId: targetSheets, fullName: state.reportData.fullName, snapshot: snapshot ? JSON.stringify(snapshot) : null };
|
exportFileExcel(state.reportData.versionId, query).then((res) => {
|
downloadByUrlReport({ url: res.data.url });
|
});
|
}
|
|
onMounted(() => {
|
init();
|
});
|
|
onUnmounted(() => {
|
handleDisposeUnit();
|
});
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper jnpf-dynamicReport-wrapper" v-loading="pageLoading">
|
<div class="tool-wrap" v-if="getHasBtn">
|
<a-tooltip title="打印" v-if="allowPrint">
|
<a-button type="text" :disabled="pageLoading" class="action-bar-btn" @click="handlePrint">
|
<i class="icon-ym icon-ym-printExample"></i>
|
</a-button>
|
</a-tooltip>
|
<a-tooltip title="导出" v-if="allowExport">
|
<a-button type="text" :disabled="pageLoading" class="action-bar-btn" @click="handleDownload()">
|
<i class="icon-ym icon-ym-btn-export1"></i>
|
</a-button>
|
</a-tooltip>
|
</div>
|
<div class="query-wrap" v-if="searchSchemas?.length">
|
<BasicForm @register="registerSearchForm" :schemas="searchSchemas" @submit="handleSearchSubmit" @reset="searchFormSubmit" class="search-form" />
|
</div>
|
<div class="content-main">
|
<JnpfUniver ref="jnpfUniverRef" :key="jnpfUniverKey" />
|
<div id="floatEchartVMContent" class="cell-echarts-vm-content">
|
<JnpfUniverFloatEchartVM ref="jnpfUniverFloatEchartVMRef" univer-create-mode="preview" />
|
</div>
|
<div id="cellEchartsContent" class="cell-echarts-content">
|
<JnpfUniverCellEchart ref="jnpfUniverCellEchartRef" univer-create-mode="preview" />
|
</div>
|
</div>
|
<ReportPrint @register="registerReportPrint" />
|
</div>
|
</template>
|
|
<style lang="scss">
|
.jnpf-dynamicReport-wrapper {
|
display: flex;
|
flex-direction: column;
|
background-color: var(--component-background);
|
|
.tool-wrap {
|
height: 53px;
|
padding: 10px;
|
border-bottom: 1px solid var(--border-color-base1);
|
}
|
|
.query-wrap {
|
padding: 10px 10px 0;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.content-main {
|
position: relative;
|
flex: 1;
|
width: 100%;
|
height: 100%;
|
|
.cell-echarts-content {
|
position: absolute;
|
inset: 0;
|
z-index: -1;
|
}
|
|
.cell-echarts-vm-content {
|
position: absolute;
|
inset: 0;
|
z-index: -2;
|
}
|
}
|
}
|
</style>
|