refactor: restore zq admin design baseline
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "@zq/univer",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
},
|
||||
"./style": {
|
||||
"development": "./src/components/Report/style/index.scss",
|
||||
"default": "./src/components/Report/style/index.scss"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs-pro/engine-pivot": "0.8.3",
|
||||
"@univerjs-pro/sheets-pivot": "0.8.3",
|
||||
"@univerjs-pro/sheets-pivot-ui": "0.8.3",
|
||||
"@univerjs/core": "0.8.3",
|
||||
"@univerjs/data-validation": "0.8.3",
|
||||
"@univerjs/design": "0.8.3",
|
||||
"@univerjs/docs": "0.8.3",
|
||||
"@univerjs/docs-drawing": "0.8.3",
|
||||
"@univerjs/docs-drawing-ui": "0.8.3",
|
||||
"@univerjs/docs-ui": "0.8.3",
|
||||
"@univerjs/drawing": "0.8.3",
|
||||
"@univerjs/drawing-ui": "0.8.3",
|
||||
"@univerjs/engine-formula": "0.8.3",
|
||||
"@univerjs/engine-render": "0.8.3",
|
||||
"@univerjs/facade": "0.5.5",
|
||||
"@univerjs/find-replace": "0.8.3",
|
||||
"@univerjs/icons": "0.4.4",
|
||||
"@univerjs/protocol": "0.1.46",
|
||||
"@univerjs/sheets": "0.8.3",
|
||||
"@univerjs/sheets-conditional-formatting": "0.8.3",
|
||||
"@univerjs/sheets-conditional-formatting-ui": "0.8.3",
|
||||
"@univerjs/sheets-crosshair-highlight": "0.8.3",
|
||||
"@univerjs/sheets-data-validation": "0.8.3",
|
||||
"@univerjs/sheets-data-validation-ui": "0.8.3",
|
||||
"@univerjs/sheets-drawing": "0.8.3",
|
||||
"@univerjs/sheets-drawing-ui": "0.8.3",
|
||||
"@univerjs/sheets-filter": "0.8.3",
|
||||
"@univerjs/sheets-filter-ui": "0.8.3",
|
||||
"@univerjs/sheets-find-replace": "0.8.3",
|
||||
"@univerjs/sheets-formula": "0.8.3",
|
||||
"@univerjs/sheets-formula-ui": "0.8.3",
|
||||
"@univerjs/sheets-hyper-link": "0.8.3",
|
||||
"@univerjs/sheets-hyper-link-ui": "0.8.3",
|
||||
"@univerjs/sheets-numfmt": "0.8.3",
|
||||
"@univerjs/sheets-numfmt-ui": "0.8.3",
|
||||
"@univerjs/sheets-sort": "0.8.3",
|
||||
"@univerjs/sheets-sort-ui": "0.8.3",
|
||||
"@univerjs/sheets-thread-comment": "0.8.3",
|
||||
"@univerjs/sheets-thread-comment-ui": "0.8.3",
|
||||
"@univerjs/sheets-ui": "0.8.3",
|
||||
"@univerjs/sheets-zen-editor": "0.8.3",
|
||||
"@univerjs/thread-comment": "0.8.3",
|
||||
"@univerjs/thread-comment-ui": "0.8.3",
|
||||
"@univerjs/ui": "0.8.3",
|
||||
"@univerjs/uniscript": "0.8.3",
|
||||
"@univerjs/watermark": "0.8.3",
|
||||
"@vben/locales": "workspace:*",
|
||||
"element-plus": "catalog:",
|
||||
"echarts": "catalog:",
|
||||
"jsbarcode": "^3.12.1",
|
||||
"lodash-es": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"vue": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@univerjs/vite-plugin": "0.5.1",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { ICellData, IWorkbookData } from '@univerjs/core';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export interface DesignStateProps {
|
||||
[prop: string]: any;
|
||||
activeWorkbook: any;
|
||||
|
||||
activeWorkbookId: any;
|
||||
|
||||
activeWorksheet: any;
|
||||
|
||||
activeWorksheetId: any;
|
||||
beforeCommandExecuteMonitor: any;
|
||||
commandExecuteMonitor: any;
|
||||
configDialogCellTarget: null;
|
||||
|
||||
containerEleId: any;
|
||||
|
||||
dialogCellInstance: any;
|
||||
zqUniverAPI: any;
|
||||
|
||||
loading: boolean;
|
||||
openDialogCell: boolean;
|
||||
selectionChangeMonitor: any;
|
||||
|
||||
univer: any;
|
||||
univerCreateMode: 'design' | 'preview' | 'print';
|
||||
}
|
||||
|
||||
export interface CreateUnitProps {
|
||||
cellEcharts?: Record<string, any>;
|
||||
|
||||
defaultActiveSheetId?: string;
|
||||
floatEcharts?: Record<string, any>;
|
||||
floatImages?: Record<string, any>;
|
||||
loading?: boolean;
|
||||
|
||||
mode?: 'design' | 'preview' | 'print';
|
||||
|
||||
readonly?: boolean;
|
||||
snapshot?: IWorkbookData;
|
||||
uiContextMenu?: boolean;
|
||||
|
||||
uiFooter?: boolean;
|
||||
|
||||
uiHeader?: boolean;
|
||||
|
||||
watermark?: Record<string, any>;
|
||||
|
||||
darkMode?: boolean;
|
||||
|
||||
/** 应用语言:zh-CN | en-US | zh-TW,用于设计器自定义菜单文案 */
|
||||
appLocale?: string;
|
||||
}
|
||||
|
||||
export interface DeliverFloatEchartOptionProps {
|
||||
drawingId: string;
|
||||
echartType: 'bar' | 'line' | 'pie' | 'radar';
|
||||
option: echarts.EChartsOption;
|
||||
}
|
||||
|
||||
export interface DeliverFloatImageOptionProps {
|
||||
drawingId: string;
|
||||
imageType: 'BASE64' | 'URL';
|
||||
option: any;
|
||||
}
|
||||
|
||||
export interface DeliverCellDataProps {
|
||||
cellData: ICellData;
|
||||
startColumn: number;
|
||||
startRow: number;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,157 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAlphabetFromIndexRule, isNullOrUndefined } from '../univer/utils';
|
||||
|
||||
// 初始状态的默认值
|
||||
const defaultFocusFloatDomData = {
|
||||
domId: undefined,
|
||||
drawingId: undefined,
|
||||
option: {},
|
||||
};
|
||||
|
||||
export const useZqUniverStore = (id: string) => {
|
||||
return defineStore(id, () => {
|
||||
const zqUniverApiCache: any = ref(null); // 整个api实例
|
||||
const univerCreateModeCache: any = ref(null); // univer创建模式
|
||||
|
||||
const focusedFloatEchartDataCache = ref({ ...defaultFocusFloatDomData }); // 已聚焦的悬浮图表数据
|
||||
const floatEchartDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图表的数据
|
||||
const floatEchartToUniverImgDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图表转成univer图片后的数据
|
||||
|
||||
const focusedFloatImageDataCache = ref({ ...defaultFocusFloatDomData }); // 已聚焦的悬浮图片数据
|
||||
const floatImageDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图片的数据
|
||||
const floatImageToUniverImgDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图片转成univer图片后的数据
|
||||
|
||||
const dialogSelectCellDataCache: any = ref(null); // 弹窗选择区域的单元格信息
|
||||
const dialogSelectCellStateCache: any = ref(null); // 弹窗选择单元格状态 --- 值为时间戳,用于触发页面的状态更新
|
||||
|
||||
// 重置存储状态为初始值
|
||||
const resetStore = () => {
|
||||
zqUniverApiCache.value = null;
|
||||
univerCreateModeCache.value = null;
|
||||
|
||||
focusedFloatEchartDataCache.value = { ...defaultFocusFloatDomData };
|
||||
floatEchartDataCaches.value = {};
|
||||
floatEchartToUniverImgDataCaches.value = {};
|
||||
|
||||
focusedFloatImageDataCache.value = { ...defaultFocusFloatDomData };
|
||||
floatImageDataCaches.value = {};
|
||||
floatImageToUniverImgDataCaches.value = {};
|
||||
|
||||
dialogSelectCellDataCache.value = null;
|
||||
dialogSelectCellStateCache.value = null;
|
||||
};
|
||||
|
||||
// 存储univer创建模式
|
||||
const setUniverCreateModeCache = (type: string) => {
|
||||
univerCreateModeCache.value = type;
|
||||
};
|
||||
|
||||
// 存储zqUniverApi
|
||||
const setZqUniverApiCache = (value: any) => {
|
||||
zqUniverApiCache.value = value ?? null;
|
||||
};
|
||||
|
||||
// 根据drawingId获取悬浮图表信息
|
||||
const getFloatEchartDataByDrawingId = (drawingId: string) => {
|
||||
return floatEchartDataCaches.value?.[drawingId] ?? null;
|
||||
};
|
||||
|
||||
// 更新已聚焦的悬浮图表数据
|
||||
const updateFocusedFloatEchartDataCache = (value: any = {}) => {
|
||||
focusedFloatEchartDataCache.value = value;
|
||||
};
|
||||
|
||||
// 存储工作本中所有悬浮图表的数据
|
||||
const setFloatEchartDataCaches = (value: any = {}) => {
|
||||
floatEchartDataCaches.value = value;
|
||||
|
||||
zqUniverApiCache.value?.getSheetsFloatDom()?.saveFloatEchartItems(value);
|
||||
};
|
||||
|
||||
// 存储工作本中所有悬浮图表转成univer图片后的数据
|
||||
const setFloatEchartToUniverImgDataCaches = (value: any = {}) => {
|
||||
floatEchartToUniverImgDataCaches.value = value;
|
||||
};
|
||||
|
||||
// 根据drawingId获取悬浮图片信息
|
||||
const getFloatImageDataByDrawingId = (drawingId: string) => {
|
||||
return floatImageDataCaches.value?.[drawingId] ?? null;
|
||||
};
|
||||
|
||||
// 更新已聚焦的悬浮图片数据
|
||||
const updateFocusedFloatImageDataCache = (deliverConfig: any = {}) => {
|
||||
focusedFloatImageDataCache.value = deliverConfig;
|
||||
};
|
||||
|
||||
// 存储工作本中所有悬浮图片的数据
|
||||
const setFloatImageDataCaches = (value: any = {}) => {
|
||||
floatImageDataCaches.value = value;
|
||||
|
||||
zqUniverApiCache.value?.getSheetsFloatDom()?.saveFloatImageItems(value);
|
||||
};
|
||||
|
||||
// 存储工作本中所有悬浮图片转成univer图片后的数据
|
||||
const setFloatImageToUniverImgDataCaches = (value: any) => {
|
||||
floatImageToUniverImgDataCaches.value = value;
|
||||
};
|
||||
|
||||
// 存储弹窗选择单元格的值
|
||||
const setDialogSelectCellDataCache = (value: any) => {
|
||||
if (!value || isNullOrUndefined(value?.startRow) || isNullOrUndefined(value?.startColumn)) {
|
||||
dialogSelectCellDataCache.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
dialogSelectCellDataCache.value = `${getAlphabetFromIndexRule(value.startColumn)}${value.startRow + 1}`;
|
||||
};
|
||||
|
||||
// 取消弹窗选择单元格结果
|
||||
const cancelDialogSelectCell = () => {
|
||||
setDialogSelectCellDataCache(null);
|
||||
|
||||
dialogSelectCellStateCache.value = Date.now();
|
||||
};
|
||||
|
||||
// 确认弹窗选择单元格结果
|
||||
const confirmDialogSelectCell = () => {
|
||||
dialogSelectCellStateCache.value = Date.now();
|
||||
};
|
||||
|
||||
return {
|
||||
cancelDialogSelectCell,
|
||||
|
||||
confirmDialogSelectCell,
|
||||
dialogSelectCellDataCache,
|
||||
dialogSelectCellStateCache,
|
||||
|
||||
floatEchartDataCaches,
|
||||
floatEchartToUniverImgDataCaches,
|
||||
floatImageDataCaches,
|
||||
|
||||
floatImageToUniverImgDataCaches,
|
||||
focusedFloatEchartDataCache,
|
||||
|
||||
focusedFloatImageDataCache,
|
||||
getFloatEchartDataByDrawingId,
|
||||
|
||||
getFloatImageDataByDrawingId,
|
||||
resetStore,
|
||||
setDialogSelectCellDataCache,
|
||||
setFloatEchartDataCaches,
|
||||
|
||||
setFloatEchartToUniverImgDataCaches,
|
||||
setFloatImageDataCaches,
|
||||
setFloatImageToUniverImgDataCaches,
|
||||
setZqUniverApiCache,
|
||||
|
||||
setUniverCreateModeCache,
|
||||
univerCreateModeCache,
|
||||
updateFocusedFloatEchartDataCache,
|
||||
|
||||
updateFocusedFloatImageDataCache,
|
||||
};
|
||||
})();
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type { IAccessor, ICommand } from '@univerjs/core';
|
||||
|
||||
import { CommandType } from '@univerjs/core';
|
||||
|
||||
import { ZqSheetsExcelFileService } from '../../services/sheet-excel-file.service';
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsImportExcelFileOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.importExcelFile,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsDownloadExcelFileOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
accessor.get(ZqSheetsExcelFileService)?.handleDownloadExcel();
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.downloadExcelFile,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsImportCsvFileOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsExcelFileService = accessor.get(ZqSheetsExcelFileService);
|
||||
jnpfSheetsExcelFileService?.handleImportCsv();
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.importCsvFile,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import type { IAccessor, ICommand } from '@univerjs/core';
|
||||
|
||||
import { CommandType } from '@univerjs/core';
|
||||
|
||||
import { ZqSheetsFloatEchartService } from '../../services/sheet-float-echart.service';
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsInsertedFloatEchartOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertedFloatEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatBarEchartOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsFloatEchartService = accessor.get(ZqSheetsFloatEchartService);
|
||||
jnpfSheetsFloatEchartService.insertFloatEchart('ZqUniverFloatBarEchart');
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertFloatBarEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatLineEchartOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsFloatEchartService = accessor.get(ZqSheetsFloatEchartService);
|
||||
jnpfSheetsFloatEchartService.insertFloatEchart('ZqUniverFloatLineEchart');
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertFloatLineEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatPieEchartOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsFloatEchartService = accessor.get(ZqSheetsFloatEchartService);
|
||||
jnpfSheetsFloatEchartService.insertFloatEchart('ZqUniverFloatPieEchart');
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertFloatPieEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatRadarEchartOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsFloatEchartService = accessor.get(ZqSheetsFloatEchartService);
|
||||
jnpfSheetsFloatEchartService.insertFloatEchart('ZqUniverFloatRadarEchart');
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertFloatRadarEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsFocusEchartOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.focusFloatEchart,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import type { IAccessor, ICommand } from '@univerjs/core';
|
||||
|
||||
import { CommandType } from '@univerjs/core';
|
||||
|
||||
import { ZqSheetsFloatImageService } from '../../services/sheet-float-image.service';
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsInsertFloatImageOperation: ICommand = {
|
||||
handler: async (accessor: IAccessor) => {
|
||||
const jnpfSheetsFloatImageService = accessor.get(ZqSheetsFloatImageService);
|
||||
jnpfSheetsFloatImageService.insertFloatImage();
|
||||
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertFloatImage,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertedFloatImageOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.insertedFloatImage,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
|
||||
export const ZqSheetsFocusFloatImageOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.focusFloatImage,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import type { IAccessor, ICommand } from '@univerjs/core';
|
||||
|
||||
import { CommandType } from '@univerjs/core';
|
||||
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsPreviewOperation: ICommand = {
|
||||
handler: async (_: IAccessor) => {
|
||||
return true;
|
||||
},
|
||||
id: ZqCommandIds.preview,
|
||||
type: CommandType.OPERATION,
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useZqUniverStore } from '../../../store';
|
||||
|
||||
defineOptions({ name: 'ZqUniverDialogSelectCell' });
|
||||
|
||||
const props = defineProps<PropsType>();
|
||||
interface PropsType {
|
||||
value: string;
|
||||
}
|
||||
const zqUniverStore = useZqUniverStore(props?.value);
|
||||
const { dialogSelectCellDataCache } = storeToRefs(zqUniverStore);
|
||||
|
||||
// 点击取消
|
||||
const handleCancel = () => {
|
||||
zqUniverStore?.cancelDialogSelectCell();
|
||||
};
|
||||
|
||||
// 点击确认
|
||||
const handleConfirm = () => {
|
||||
zqUniverStore?.confirmDialogSelectCell();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dialog-select-cell-content">
|
||||
<div>
|
||||
<div class="univer-mb-2 univer-flex univer-items-center univer-gap-4">
|
||||
<span
|
||||
class="univer-relative univer-inline-flex univer-w-full univer-items-center univer-rounded-md univer-w-full univer-border-primary-600"
|
||||
style="width: 100%">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请选择"
|
||||
readonly
|
||||
class="univer-box-border univer-w-full univer-rounded-md univer-bg-white univer-transition-colors univer-duration-200 placeholder:univer-text-gray-400 focus:univer-border-primary-600 focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-50 dark:!univer-bg-gray-700 dark:!univer-text-white dark:focus:!univer-ring-primary-900 univer-h-8 univer-px-2 univer-text-sm univer-border-gray-200 dark:!univer-border-gray-600 univer-border-solid univer-border"
|
||||
:value="dialogSelectCellDataCache" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="univer-flex univer-gap-2" style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<button
|
||||
class="univer-box-border univer-inline-flex univer-cursor-pointer univer-select-none univer-items-center univer-justify-center univer-gap-2 univer-whitespace-nowrap univer-rounded-md univer-border univer-border-solid univer-text-sm univer-font-medium univer-transition-colors disabled:univer-pointer-events-none disabled:univer-cursor-not-allowed disabled:univer-opacity-50 [&_svg]:univer-pointer-events-none [&_svg]:univer-size-4 [&_svg]:univer-shrink-0 univer-border-gray-200 univer-bg-white univer-text-gray-700 hover:univer-bg-gray-100 active:univer-bg-gray-200 dark:!univer-border-gray-600 dark:!univer-bg-gray-700 dark:!univer-text-white dark:hover:!univer-bg-gray-600 dark:active:!univer-bg-gray-700 univer-h-8 univer-rounded-lg univer-px-2 univer-text-sm"
|
||||
@click="handleCancel">
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
class="univer-box-border univer-inline-flex univer-cursor-pointer univer-select-none univer-items-center univer-justify-center univer-gap-2 univer-whitespace-nowrap univer-rounded-md univer-border univer-border-solid univer-text-sm univer-font-medium univer-transition-colors disabled:univer-pointer-events-none disabled:univer-cursor-not-allowed disabled:univer-opacity-50 [&_svg]:univer-pointer-events-none [&_svg]:univer-size-4 [&_svg]:univer-shrink-0 univer-border-primary-600 univer-bg-primary-600 univer-text-white hover:univer-bg-primary-500 active:univer-bg-primary-700 univer-h-8 univer-rounded-lg univer-px-2 univer-text-sm"
|
||||
:disabled="!dialogSelectCellDataCache"
|
||||
style="margin-left: 10px"
|
||||
@click="handleConfirm">
|
||||
确认
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
defineOptions({ name: 'ZqUniverCellEchart' });
|
||||
|
||||
const props = defineProps<PropsType>();
|
||||
interface PropsType {
|
||||
univerCreateMode: 'design' | 'preview' | 'print';
|
||||
}
|
||||
// 渲染
|
||||
function render(option: echarts.EChartsOption, width: number, height: number) {
|
||||
// 创建一个临时容器
|
||||
const tempContainer = document.createElement('div');
|
||||
tempContainer.style.width = `${width}px`;
|
||||
tempContainer.style.height = `${height}px`;
|
||||
tempContainer.style.position = 'absolute';
|
||||
tempContainer.style.visibility = 'hidden'; // 避免影响页面视觉
|
||||
document.body.append(tempContainer);
|
||||
|
||||
let chartToImageUrl: null | string = null;
|
||||
|
||||
if (props.univerCreateMode !== 'print') {
|
||||
try {
|
||||
// 初始化 ECharts 实例
|
||||
const tempChartInstance = echarts.init(tempContainer);
|
||||
const { chartType, color, drawingId, height, styleType, type, width, ...restOption } = option;
|
||||
|
||||
// 配置图表选项
|
||||
const updateOption = {
|
||||
...restOption,
|
||||
animation: false, // 禁用动画以提高生成效率
|
||||
};
|
||||
|
||||
// 设置图表选项
|
||||
tempChartInstance.setOption(updateOption);
|
||||
|
||||
// 避免失真
|
||||
tempChartInstance.resize();
|
||||
|
||||
// 获取图表的 Base64 图片 URL
|
||||
chartToImageUrl = tempChartInstance.getDataURL({
|
||||
backgroundColor: '#f9f9f9',
|
||||
pixelRatio: 1,
|
||||
type: 'jpeg',
|
||||
});
|
||||
|
||||
// 销毁图表实例
|
||||
tempChartInstance.dispose();
|
||||
} catch (error) {
|
||||
console.error('图表生成失败:', error);
|
||||
} finally {
|
||||
// 移除临时容器
|
||||
tempContainer.remove();
|
||||
}
|
||||
|
||||
return chartToImageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
render,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: none; width: 0; height: 0"></div>
|
||||
</template>
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import * as echarts from 'echarts';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useZqUniverStore } from '../../../store';
|
||||
|
||||
interface Data {
|
||||
id: string;
|
||||
piniaStoreId: string;
|
||||
}
|
||||
interface PropsType {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'ZqUniverFloatEchart' });
|
||||
|
||||
const props = defineProps<PropsType>();
|
||||
|
||||
const floatChartContainerRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const zqUniverStore = useZqUniverStore(props?.data?.piniaStoreId);
|
||||
const { focusedFloatEchartDataCache } = storeToRefs(zqUniverStore);
|
||||
|
||||
let floatChartInstance: echarts.ECharts | null = null;
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
|
||||
// 处理窗口大小变化的函数,带300ms 防抖
|
||||
const handleResize = debounce(() => {
|
||||
floatChartInstance?.resize();
|
||||
}, 300);
|
||||
|
||||
// 初始化或更新图表
|
||||
function initialize(option: echarts.EChartsOption) {
|
||||
if (!floatChartContainerRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!floatChartInstance) {
|
||||
floatChartInstance = echarts?.init(floatChartContainerRef.value);
|
||||
}
|
||||
|
||||
floatChartInstance?.clear();
|
||||
|
||||
// 使用传入的 options 配置图表
|
||||
floatChartInstance?.setOption(option);
|
||||
|
||||
// 设置 ResizeObserver 监听容器大小变化
|
||||
if (!resizeObserver) {
|
||||
resizeObserver = new ResizeObserver(handleResize);
|
||||
resizeObserver?.observe(floatChartContainerRef.value);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { id: domId, piniaStoreId } = props.data ?? {};
|
||||
|
||||
if (!floatChartContainerRef.value || !domId || !piniaStoreId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const floatEchartDataCaches = zqUniverStore?.floatEchartDataCaches ?? {};
|
||||
|
||||
const targetOption = Object.values(floatEchartDataCaches as Record<string, { domId: string; option: any }>)?.find(item => item?.domId === domId)?.option;
|
||||
initialize(targetOption);
|
||||
});
|
||||
|
||||
// 销毁
|
||||
onBeforeUnmount(() => {
|
||||
if (resizeObserver && floatChartContainerRef.value) {
|
||||
resizeObserver?.unobserve(floatChartContainerRef.value);
|
||||
}
|
||||
|
||||
resizeObserver?.disconnect();
|
||||
floatChartInstance?.dispose();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => focusedFloatEchartDataCache.value,
|
||||
value => {
|
||||
const { domId, drawingId, option } = value;
|
||||
|
||||
if (!domId && !drawingId) {
|
||||
// store置空的情况下,不响应
|
||||
return;
|
||||
}
|
||||
|
||||
const containerId = floatChartContainerRef.value?.getAttribute('id');
|
||||
if (domId !== containerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialize(option);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="floatChartContainerRef" :id="data.id" class="zq-univer-float-echart-ele"></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zq-univer-float-echart-ele {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
</style>
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
defineOptions({ name: 'ZqUniverFloatEchartVM' });
|
||||
|
||||
const props = defineProps<PropsType>();
|
||||
interface PropsType {
|
||||
univerCreateMode: 'design' | 'preview' | 'print';
|
||||
}
|
||||
// 渲染
|
||||
function render(domId: string, option: echarts.EChartsOption, width: number, height: number) {
|
||||
// 创建一个临时容器
|
||||
const tempContainer = document.createElement('div');
|
||||
tempContainer.style.width = `${width}px`;
|
||||
tempContainer.style.height = `${height}px`;
|
||||
tempContainer.style.position = 'absolute';
|
||||
tempContainer.style.visibility = 'hidden'; // 避免影响页面视觉
|
||||
document.body.append(tempContainer);
|
||||
|
||||
let chartToImageUrl: null | string = null;
|
||||
|
||||
if (props.univerCreateMode === 'preview') {
|
||||
try {
|
||||
// 初始化 ECharts 实例
|
||||
const tempChartInstance = echarts.init(tempContainer);
|
||||
|
||||
// 配置图表选项
|
||||
const updateOption = {
|
||||
...option,
|
||||
animation: false, // 禁用动画以提高生成效率
|
||||
};
|
||||
|
||||
const { color, ...restOption } = updateOption;
|
||||
|
||||
// 设置图表选项
|
||||
tempChartInstance.setOption(restOption);
|
||||
|
||||
// 获取图表的 Base64 图片 URL
|
||||
chartToImageUrl = tempChartInstance.getDataURL({
|
||||
backgroundColor: '#f9f9f9',
|
||||
type: 'jpeg',
|
||||
});
|
||||
|
||||
// 销毁图表实例
|
||||
tempChartInstance.dispose();
|
||||
} catch (error) {
|
||||
console.error('图表生成失败:', error);
|
||||
} finally {
|
||||
// 移除临时容器
|
||||
tempContainer.remove();
|
||||
}
|
||||
|
||||
return {
|
||||
domId,
|
||||
source: chartToImageUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
render,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: none; width: 0; height: 0"></div>
|
||||
</template>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
import { useZqUniverStore } from '../../../store';
|
||||
|
||||
interface Data {
|
||||
id: string;
|
||||
piniaStoreId: string;
|
||||
}
|
||||
interface PropsType {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'ZqUniverFloatImage' });
|
||||
|
||||
const props = defineProps<PropsType>();
|
||||
|
||||
const floatImageContainerRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const zqUniverStore = useZqUniverStore(props?.data?.piniaStoreId);
|
||||
const { floatImageToUniverImgDataCaches, focusedFloatImageDataCache, univerCreateModeCache } = storeToRefs(zqUniverStore);
|
||||
|
||||
// 初始化或更新图表
|
||||
function initialize(option: any) {
|
||||
if (!floatImageContainerRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageEle = floatImageContainerRef.value?.querySelector('#imageEle') as HTMLImageElement | null;
|
||||
if (!imageEle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { alt = '', src = '' } = option ?? {};
|
||||
imageEle.setAttribute('src', src);
|
||||
imageEle.setAttribute('alt', alt);
|
||||
|
||||
// 非设计状态下,图片直接转成univer图片
|
||||
if (univerCreateModeCache.value !== 'design') {
|
||||
const { id: domId } = props.data ?? {};
|
||||
|
||||
if (domId) {
|
||||
const caches = {
|
||||
...floatImageToUniverImgDataCaches.value,
|
||||
[domId]: src,
|
||||
};
|
||||
|
||||
zqUniverStore?.setFloatImageToUniverImgDataCaches(caches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { id: domId, piniaStoreId } = props.data ?? {};
|
||||
|
||||
if (!floatImageContainerRef.value || !domId || !piniaStoreId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const floatImageDataCaches = zqUniverStore?.floatImageDataCaches ?? {};
|
||||
|
||||
const targetOption = Object.values(floatImageDataCaches as Record<string, { domId: string; option: any }>)?.find(item => item?.domId === domId)?.option;
|
||||
initialize(targetOption);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => focusedFloatImageDataCache.value,
|
||||
value => {
|
||||
const { domId, drawingId, option } = value;
|
||||
|
||||
if (!domId && !drawingId) {
|
||||
// store置空的情况下,不响应
|
||||
return;
|
||||
}
|
||||
|
||||
const containerId = floatImageContainerRef.value?.getAttribute('id');
|
||||
if (domId !== containerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialize(option);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="floatImageContainerRef" :id="data.id" class="zq-univer-float-image-ele">
|
||||
<img id="imageEle" src="" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zq-univer-float-image-ele {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
|
||||
import { MenuItemType } from '@univerjs/ui';
|
||||
|
||||
import {
|
||||
ZqSheetsDownloadExcelFileOperation,
|
||||
ZqSheetsImportCsvFileOperation,
|
||||
ZqSheetsImportExcelFileOperation,
|
||||
} from '../../commands/operations/sheet-excel-file.operation';
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsExcelFileMenuFactory = (): IMenuSelectorItem => {
|
||||
return {
|
||||
icon: 'DirectExportSingle',
|
||||
id: ZqCommandIds.excelFileOperations,
|
||||
tooltip: 'jnpfSheetExcelFileMenu.tooltip',
|
||||
type: MenuItemType.SUBITEMS,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsImportExcelFileMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'UploadSingle',
|
||||
id: ZqSheetsImportExcelFileOperation.id,
|
||||
title: 'jnpfSheetImportExcelFileMenu.title',
|
||||
tooltip: 'jnpfSheetImportExcelFileMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsDownloadExcelFileMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'DirectExportSingle',
|
||||
id: ZqSheetsDownloadExcelFileOperation.id,
|
||||
title: 'jnpfSheetDownloadExcelFileMenu.title',
|
||||
tooltip: 'jnpfSheetDownloadExcelFileMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsImportCsvFileMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'UpperFloorSingle',
|
||||
id: ZqSheetsImportCsvFileOperation.id,
|
||||
title: 'jnpfSheetImportCsvFileMenu.title',
|
||||
tooltip: 'jnpfSheetImportCsvFileMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
|
||||
import { MenuItemType } from '@univerjs/ui';
|
||||
|
||||
import {
|
||||
ZqSheetsInsertFloatBarEchartOperation,
|
||||
ZqSheetsInsertFloatLineEchartOperation,
|
||||
ZqSheetsInsertFloatPieEchartOperation,
|
||||
ZqSheetsInsertFloatRadarEchartOperation,
|
||||
} from '../../commands/operations/sheet-float-echart.operation';
|
||||
import { ZqCommandIds } from '../../utils/define';
|
||||
|
||||
export const ZqSheetsFloatEchartMenuFactory = (): IMenuSelectorItem => {
|
||||
return {
|
||||
icon: 'SystemSingle',
|
||||
id: ZqCommandIds.floatEchartOperations,
|
||||
tooltip: 'jnpfSheetFloatEchartMenu.tooltip',
|
||||
type: MenuItemType.SUBITEMS,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatBarEchartMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'ChartSingle',
|
||||
id: ZqSheetsInsertFloatBarEchartOperation.id,
|
||||
title: 'jnpfSheetInsertFloatBarEchartMenu.title',
|
||||
tooltip: 'jnpfSheetInsertFloatBarEchartMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatLineEchartMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'LineChartSingle',
|
||||
id: ZqSheetsInsertFloatLineEchartOperation.id,
|
||||
title: 'jnpfSheetInsertFloatLineEchartMenu.title',
|
||||
tooltip: 'jnpfSheetInsertFloatLineEchartMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatPieEchartMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'PieChartSingle',
|
||||
id: ZqSheetsInsertFloatPieEchartOperation.id,
|
||||
title: 'jnpfSheetInsertFloatPieEchartMenu.title',
|
||||
tooltip: 'jnpfSheetInsertFloatPieEchartMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
|
||||
export const ZqSheetsInsertFloatRadarEchartMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'RadarChartSingle',
|
||||
id: ZqSheetsInsertFloatRadarEchartOperation.id,
|
||||
title: 'jnpfSheetInsertFloatRadarEchartMenu.title',
|
||||
tooltip: 'jnpfSheetInsertFloatRadarEchartMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import type { IMenuButtonItem } from '@univerjs/ui';
|
||||
import { MenuItemType } from '@univerjs/ui';
|
||||
|
||||
import { ZqSheetsInsertFloatImageOperation } from '../../commands/operations/sheet-float-image.operation';
|
||||
|
||||
export const ZqSheetsInsertFloatImageMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
id: ZqSheetsInsertFloatImageOperation.id,
|
||||
title: 'jnpfSheetInsertFloatImageMenu.title',
|
||||
tooltip: 'jnpfSheetInsertFloatImageMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import type { IMenuButtonItem } from '@univerjs/ui';
|
||||
import { MenuItemType } from '@univerjs/ui';
|
||||
|
||||
import { ZqSheetsPreviewOperation } from '../../commands/operations/sheet-preview.operation';
|
||||
|
||||
export const ZqSheetsPreviewMenuFactory = (): IMenuButtonItem => {
|
||||
return {
|
||||
icon: 'ViewModeSingle',
|
||||
id: ZqSheetsPreviewOperation.id,
|
||||
title: 'jnpfSheetPreviewMenu.title',
|
||||
tooltip: 'jnpfSheetPreviewMenu.tooltip',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { ComponentManager } from '@univerjs/ui';
|
||||
|
||||
import univerDialogSelectCellComponent from '../components/Dialog/selectCell.vue';
|
||||
import { ZqUniverDialogSelectCellKey } from '../utils/define';
|
||||
|
||||
export class ZqSheetsDialogController extends Disposable {
|
||||
constructor(@Inject(ComponentManager) private readonly _componentManager: ComponentManager) {
|
||||
super();
|
||||
|
||||
this._registerComponents();
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
// 注册选区组件进来
|
||||
this.disposeWithMe(this._componentManager.register(ZqUniverDialogSelectCellKey, univerDialogSelectCellComponent, { framework: 'vue3' }));
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { UploadSingle, UpperFloorSingle } from '@univerjs/icons';
|
||||
import { ComponentManager, IMenuManagerService, RibbonStartGroup } from '@univerjs/ui';
|
||||
|
||||
import {
|
||||
ZqSheetsDownloadExcelFileOperation,
|
||||
ZqSheetsImportCsvFileOperation,
|
||||
ZqSheetsImportExcelFileOperation,
|
||||
} from '../commands/operations/sheet-excel-file.operation';
|
||||
import { ZqSheetsImportExcelFileMenuFactory } from '../controllers/menus/sheet-excel-file.menu';
|
||||
|
||||
export class ZqSheetsExcelFileController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommands();
|
||||
this._registerComponents();
|
||||
this._initMenus();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
[ZqSheetsImportExcelFileOperation, ZqSheetsDownloadExcelFileOperation, ZqSheetsImportCsvFileOperation].forEach(command => {
|
||||
this.disposeWithMe(this._commandService.registerCommand(command));
|
||||
});
|
||||
}
|
||||
|
||||
private _initMenus(): void {
|
||||
this._menuManagerService.mergeMenu({
|
||||
[RibbonStartGroup.OTHERS]: {
|
||||
[ZqSheetsImportExcelFileOperation.id]: {
|
||||
menuItemFactory: ZqSheetsImportExcelFileMenuFactory,
|
||||
order: 101,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
// 注册按钮图标
|
||||
this.disposeWithMe(this._componentManager.register('UploadSingle', UploadSingle));
|
||||
// this.disposeWithMe(this._componentManager.register('ExportSingle', ExportSingle));
|
||||
this.disposeWithMe(this._componentManager.register('UpperFloorSingle', UpperFloorSingle));
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { ChartSingle, LineChartSingle, PieChartSingle, RadarChartSingle, SystemSingle } from '@univerjs/icons';
|
||||
import { ComponentManager, IMenuManagerService, RibbonInsertGroup } from '@univerjs/ui';
|
||||
|
||||
import {
|
||||
ZqSheetsFocusEchartOperation,
|
||||
ZqSheetsInsertedFloatEchartOperation,
|
||||
ZqSheetsInsertFloatBarEchartOperation,
|
||||
ZqSheetsInsertFloatLineEchartOperation,
|
||||
ZqSheetsInsertFloatPieEchartOperation,
|
||||
ZqSheetsInsertFloatRadarEchartOperation,
|
||||
} from '../commands/operations/sheet-float-echart.operation';
|
||||
import univerFloatEchartComponent from '../components/Echart/float.vue';
|
||||
import { ZqCommandIds, ZqUniverFloatEchartKey } from '../utils/define';
|
||||
import {
|
||||
ZqSheetsFloatEchartMenuFactory,
|
||||
ZqSheetsInsertFloatBarEchartMenuFactory,
|
||||
ZqSheetsInsertFloatLineEchartMenuFactory,
|
||||
ZqSheetsInsertFloatPieEchartMenuFactory,
|
||||
ZqSheetsInsertFloatRadarEchartMenuFactory,
|
||||
} from './menus/sheet-float-echart.menu';
|
||||
|
||||
export class ZqSheetsFloatEchartController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommands();
|
||||
this._registerComponents();
|
||||
this._initMenus();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
[
|
||||
ZqSheetsInsertFloatBarEchartOperation,
|
||||
ZqSheetsInsertFloatLineEchartOperation,
|
||||
ZqSheetsInsertFloatPieEchartOperation,
|
||||
ZqSheetsInsertFloatRadarEchartOperation,
|
||||
ZqSheetsInsertedFloatEchartOperation,
|
||||
ZqSheetsFocusEchartOperation,
|
||||
].forEach(command => {
|
||||
this.disposeWithMe(this._commandService.registerCommand(command));
|
||||
});
|
||||
}
|
||||
|
||||
private _initMenus(): void {
|
||||
this._menuManagerService.mergeMenu({
|
||||
// 主菜单
|
||||
[RibbonInsertGroup.MEDIA]: {
|
||||
[ZqCommandIds.floatEchartOperations]: {
|
||||
[ZqSheetsInsertFloatBarEchartOperation.id]: {
|
||||
menuItemFactory: ZqSheetsInsertFloatBarEchartMenuFactory,
|
||||
order: 0,
|
||||
},
|
||||
[ZqSheetsInsertFloatLineEchartOperation.id]: {
|
||||
menuItemFactory: ZqSheetsInsertFloatLineEchartMenuFactory,
|
||||
order: 1,
|
||||
},
|
||||
[ZqSheetsInsertFloatPieEchartOperation.id]: {
|
||||
menuItemFactory: ZqSheetsInsertFloatPieEchartMenuFactory,
|
||||
order: 2,
|
||||
},
|
||||
[ZqSheetsInsertFloatRadarEchartOperation.id]: {
|
||||
menuItemFactory: ZqSheetsInsertFloatRadarEchartMenuFactory,
|
||||
order: 3,
|
||||
},
|
||||
menuItemFactory: ZqSheetsFloatEchartMenuFactory,
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
// 注册图表组件进来
|
||||
this.disposeWithMe(this._componentManager.register(ZqUniverFloatEchartKey, univerFloatEchartComponent, { framework: 'vue3' }));
|
||||
|
||||
// 注册按钮图标
|
||||
this.disposeWithMe(this._componentManager.register('SystemSingle', SystemSingle));
|
||||
this.disposeWithMe(this._componentManager.register('ChartSingle', ChartSingle));
|
||||
this.disposeWithMe(this._componentManager.register('LineChartSingle', LineChartSingle));
|
||||
this.disposeWithMe(this._componentManager.register('PieChartSingle', PieChartSingle));
|
||||
this.disposeWithMe(this._componentManager.register('RadarChartSingle', RadarChartSingle));
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { SHEETS_IMAGE_MENU_ID } from '@univerjs/sheets-drawing-ui';
|
||||
import { ComponentManager, IMenuManagerService, RibbonInsertGroup } from '@univerjs/ui';
|
||||
|
||||
import {
|
||||
ZqSheetsFocusFloatImageOperation,
|
||||
ZqSheetsInsertedFloatImageOperation,
|
||||
ZqSheetsInsertFloatImageOperation,
|
||||
} from '../commands/operations/sheet-float-image.operation';
|
||||
import univerFloatImageComponent from '../components/Image/float.vue';
|
||||
import { ZqUniverFloatImageKey } from '../utils/define';
|
||||
import { ZqSheetsInsertFloatImageMenuFactory } from './menus/sheet-float-image.menu';
|
||||
|
||||
export class ZqSheetsFloatImageController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommands();
|
||||
this._registerComponents();
|
||||
this._initMenus();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
[ZqSheetsInsertFloatImageOperation, ZqSheetsInsertedFloatImageOperation, ZqSheetsFocusFloatImageOperation].forEach(command => {
|
||||
this.disposeWithMe(this._commandService.registerCommand(command));
|
||||
});
|
||||
}
|
||||
|
||||
private _initMenus(): void {
|
||||
this._menuManagerService.mergeMenu({
|
||||
[RibbonInsertGroup.MEDIA]: {
|
||||
[SHEETS_IMAGE_MENU_ID]: {
|
||||
[ZqSheetsInsertFloatImageOperation.id]: {
|
||||
menuItemFactory: ZqSheetsInsertFloatImageMenuFactory,
|
||||
order: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
// 注册图片组件进来
|
||||
this.disposeWithMe(this._componentManager.register(ZqUniverFloatImageKey, univerFloatImageComponent, { framework: 'vue3' }));
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { ViewModeSingle } from '@univerjs/icons';
|
||||
import { ComponentManager, IMenuManagerService, RibbonStartGroup } from '@univerjs/ui';
|
||||
|
||||
import { ZqSheetsPreviewOperation } from '../commands/operations/sheet-preview.operation';
|
||||
import { ZqSheetsPreviewMenuFactory } from '../controllers/menus/sheet-preview.menu';
|
||||
|
||||
export class ZqSheetsPreviewController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommands();
|
||||
this._registerComponents();
|
||||
this._initMenus();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
[ZqSheetsPreviewOperation].forEach(command => {
|
||||
this.disposeWithMe(this._commandService.registerCommand(command));
|
||||
});
|
||||
}
|
||||
|
||||
private _initMenus(): void {
|
||||
this._menuManagerService.mergeMenu({
|
||||
// 主菜单
|
||||
[RibbonStartGroup.OTHERS]: {
|
||||
[ZqSheetsPreviewOperation.id]: {
|
||||
menuItemFactory: ZqSheetsPreviewMenuFactory,
|
||||
order: 99,
|
||||
},
|
||||
},
|
||||
// 右键菜单
|
||||
// [ContextMenuPosition.MAIN_AREA]: {
|
||||
// [ContextMenuGroup.OTHERS]: {
|
||||
// [ZqSheetsPreviewOperation.id]: {
|
||||
// order: 0,
|
||||
// menuItemFactory: ZqSheetsPreviewMenuFactory,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
});
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
// 注册按钮图标
|
||||
this.disposeWithMe(this._componentManager.register('ViewModeSingle', ViewModeSingle));
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import type { IScale } from '@univerjs/core';
|
||||
import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
|
||||
import { DEFAULT_FONTFACE_PLANE, FIX_ONE_PIXEL_BLUR_OFFSET, getColor, SheetExtension } from '@univerjs/engine-render';
|
||||
|
||||
type CellCoordinates = Array<{ col: number; row: number }>; // 单元格坐标数组
|
||||
|
||||
interface ClearSheetFillDirectionProps {
|
||||
key: string;
|
||||
sheetId: string;
|
||||
}
|
||||
|
||||
interface SetSheetFillDirectionProps extends ClearSheetFillDirectionProps {
|
||||
downArrowCells?: CellCoordinates;
|
||||
rightArrowCells?: CellCoordinates;
|
||||
}
|
||||
|
||||
interface RenderArrowsProps {
|
||||
arrow: string;
|
||||
cells: CellCoordinates;
|
||||
colWidths: number[];
|
||||
ctx: UniverRenderingContext;
|
||||
endCol: number;
|
||||
endRow: number;
|
||||
offsets: { xOffset: number; yOffset: number };
|
||||
rowHeights: number[];
|
||||
startCol: number;
|
||||
startRow: number;
|
||||
}
|
||||
|
||||
// 定义主要扩展类,用于在表格中绘制自定义内容
|
||||
export class SetSheetFillDirectionExtension extends SheetExtension {
|
||||
override uKey: string;
|
||||
// 设置扩展的层级(z-index),必须大于 50
|
||||
override get zIndex() {
|
||||
return 50;
|
||||
}
|
||||
private readonly _downArrowCells: CellCoordinates; // 渲染 ↓ 的条件数组
|
||||
private readonly _rightArrowCells: CellCoordinates; // 渲染 → 的条件数组
|
||||
|
||||
private readonly _sheetId: string; // 用于存储动态传入的 SheetId
|
||||
|
||||
constructor(props: SetSheetFillDirectionProps) {
|
||||
super();
|
||||
|
||||
const { downArrowCells, key, rightArrowCells, sheetId } = props ?? {};
|
||||
this.uKey = key;
|
||||
this._sheetId = sheetId;
|
||||
this._rightArrowCells = rightArrowCells ?? [];
|
||||
this._downArrowCells = downArrowCells ?? [];
|
||||
}
|
||||
|
||||
// 重写绘制方法
|
||||
override draw(ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
|
||||
if (!this.isValidSkeleton(skeleton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._rightArrowCells?.length && !this._downArrowCells?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { columnTotalWidth, columnWidthAccumulation, rowColumnSegment, rowHeightAccumulation, rowTotalHeight } = skeleton;
|
||||
const { endColumn, endRow, startColumn, startRow } = rowColumnSegment;
|
||||
if (!rowHeightAccumulation || !columnWidthAccumulation || columnTotalWidth === undefined || rowTotalHeight === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupCanvas(ctx);
|
||||
|
||||
// 绘制 → 箭头
|
||||
this.renderArrows({
|
||||
arrow: '→',
|
||||
cells: this._rightArrowCells,
|
||||
colWidths: columnWidthAccumulation,
|
||||
ctx,
|
||||
endCol: endColumn,
|
||||
endRow,
|
||||
offsets: { xOffset: 2, yOffset: -2 },
|
||||
rowHeights: rowHeightAccumulation,
|
||||
startCol: startColumn,
|
||||
startRow,
|
||||
});
|
||||
|
||||
// 绘制 ↓ 箭头
|
||||
this.renderArrows({
|
||||
arrow: '↓',
|
||||
cells: this._downArrowCells,
|
||||
colWidths: columnWidthAccumulation,
|
||||
ctx,
|
||||
endCol: endColumn,
|
||||
endRow,
|
||||
offsets: { xOffset: 0, yOffset: 1 },
|
||||
rowHeights: rowHeightAccumulation,
|
||||
startCol: startColumn,
|
||||
startRow,
|
||||
});
|
||||
|
||||
// 绘制分隔线
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 验证骨架和必要属性是否有效
|
||||
private isValidSkeleton(skeleton: SpreadsheetSkeleton): boolean {
|
||||
// 如果骨架信息不存在,则退出绘制
|
||||
if (!skeleton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 限制扩展仅在指定的工作表上生效
|
||||
return skeleton?.worksheet?.getSheetId() === this._sheetId;
|
||||
}
|
||||
|
||||
// 渲染箭头
|
||||
private renderArrows(props: RenderArrowsProps) {
|
||||
const {
|
||||
arrow,
|
||||
cells,
|
||||
colWidths,
|
||||
ctx,
|
||||
endCol,
|
||||
endRow,
|
||||
offsets = { xOffset: 0, yOffset: 0 }, // 默认偏移量
|
||||
rowHeights,
|
||||
startCol,
|
||||
startRow,
|
||||
} = props ?? {};
|
||||
|
||||
for (const { col, row } of cells) {
|
||||
if (row >= startRow - 1 && row <= endRow && col >= startCol - 1 && col <= endCol) {
|
||||
const rowStart = rowHeights[row - 1] || 0;
|
||||
const colStart = colWidths[col - 1] || 0;
|
||||
ctx.fillText(arrow, colStart + offsets.xOffset, rowStart + offsets.yOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 Canvas 的绘图属性
|
||||
private setupCanvas(ctx: UniverRenderingContext) {
|
||||
// 设置背景色为浅灰色
|
||||
ctx.fillStyle = getColor([255, 255, 255]);
|
||||
// 设置文本水平对齐方式
|
||||
ctx.textAlign = 'left';
|
||||
// 设置文本垂直对齐方式
|
||||
ctx.textBaseline = 'top';
|
||||
// 设置文本颜色为黑色
|
||||
ctx.fillStyle = getColor([0, 0, 0])!;
|
||||
// 开始绘制路径
|
||||
ctx.beginPath();
|
||||
// 设置线宽为 1
|
||||
ctx.lineWidth = 1;
|
||||
// 防止边界模糊的偏移值,使绘制的边界在高分辨率屏幕下更清晰
|
||||
ctx.translateWithPrecisionRatio(FIX_ONE_PIXEL_BLUR_OFFSET, FIX_ONE_PIXEL_BLUR_OFFSET);
|
||||
// 设置分隔线颜色为浅灰色
|
||||
ctx.strokeStyle = getColor([217, 217, 217]);
|
||||
// 设置字体样式
|
||||
ctx.font = `10px ${DEFAULT_FONTFACE_PLANE}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 定义清理扩展类,用于覆盖并清除扩展效果
|
||||
export class ClearSheetFillDirectionExtension extends SheetExtension {
|
||||
override uKey: string;
|
||||
private readonly _sheetId: string; // 用于存储动态传入的 SheetId
|
||||
|
||||
constructor(props: ClearSheetFillDirectionProps) {
|
||||
super();
|
||||
|
||||
const { key, sheetId } = props ?? {};
|
||||
this.uKey = key;
|
||||
this._sheetId = sheetId; // 将传入的 SheetId 存储到类中
|
||||
}
|
||||
|
||||
// 空的绘制方法,不进行任何绘制操作
|
||||
override draw(_ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
|
||||
if (skeleton?.worksheet?.getSheetId() !== this._sheetId) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
import type { IScale } from '@univerjs/core';
|
||||
import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
|
||||
import {
|
||||
DEFAULT_FONTFACE_PLANE,
|
||||
FIX_ONE_PIXEL_BLUR_OFFSET,
|
||||
getColor,
|
||||
SheetExtension,
|
||||
} from '@univerjs/engine-render';
|
||||
|
||||
type CellCoordinates = Array<{ col: number; row: number }>; // 单元格坐标数组
|
||||
|
||||
interface ClearSheetRelationCellProps {
|
||||
key: string;
|
||||
sheetId: string;
|
||||
}
|
||||
|
||||
interface SetSheetRelationCellProps extends ClearSheetRelationCellProps {
|
||||
downArrowCells?: CellCoordinates;
|
||||
rightArrowCells?: CellCoordinates;
|
||||
}
|
||||
|
||||
interface RenderArrowsProps {
|
||||
arrow: string;
|
||||
cells: CellCoordinates;
|
||||
colWidths: number[];
|
||||
ctx: UniverRenderingContext;
|
||||
endCol: number;
|
||||
endRow: number;
|
||||
offsetsX?: number;
|
||||
offsetsY?: number;
|
||||
rowHeights: number[];
|
||||
startCol: number;
|
||||
startRow: number;
|
||||
}
|
||||
|
||||
// 定义主要扩展类,用于在表格中绘制自定义内容
|
||||
export class SetSheetRelationCellExtension extends SheetExtension {
|
||||
override uKey: string;
|
||||
// 设置扩展的层级(z-index),必须大于 50
|
||||
override get zIndex() {
|
||||
return 50;
|
||||
}
|
||||
private readonly _downArrowCells: CellCoordinates; // 渲染 ↓ 的条件数组
|
||||
private readonly _rightArrowCells: CellCoordinates; // 渲染 → 的条件数组
|
||||
|
||||
private readonly _sheetId: string; // 用于存储动态传入的 SheetId
|
||||
|
||||
constructor(props: SetSheetRelationCellProps) {
|
||||
super();
|
||||
|
||||
const { downArrowCells, key, rightArrowCells, sheetId } = props ?? {};
|
||||
this.uKey = key;
|
||||
this._sheetId = sheetId;
|
||||
this._rightArrowCells = rightArrowCells ?? [];
|
||||
this._downArrowCells = downArrowCells ?? [];
|
||||
}
|
||||
|
||||
// 重写绘制方法
|
||||
override draw(ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
|
||||
if (!this.isValidSkeleton(skeleton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._rightArrowCells?.length && !this._downArrowCells?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { columnTotalWidth, columnWidthAccumulation, rowColumnSegment, rowHeightAccumulation, rowTotalHeight } = skeleton;
|
||||
const { endColumn, endRow, startColumn, startRow } = rowColumnSegment;
|
||||
if (!rowHeightAccumulation || !columnWidthAccumulation || columnTotalWidth === undefined || rowTotalHeight === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupCanvas(ctx);
|
||||
|
||||
// 绘制 → 箭头
|
||||
this.renderArrows({
|
||||
arrow: '→',
|
||||
cells: this._rightArrowCells,
|
||||
colWidths: columnWidthAccumulation,
|
||||
ctx,
|
||||
endCol: endColumn,
|
||||
endRow,
|
||||
offsetsX: undefined,
|
||||
offsetsY: -2,
|
||||
rowHeights: rowHeightAccumulation,
|
||||
startCol: startColumn,
|
||||
startRow,
|
||||
});
|
||||
|
||||
// 绘制 ↓ 箭头
|
||||
this.renderArrows({
|
||||
arrow: '↓',
|
||||
cells: this._downArrowCells,
|
||||
colWidths: columnWidthAccumulation,
|
||||
ctx,
|
||||
endCol: endColumn,
|
||||
endRow,
|
||||
offsetsX: 0,
|
||||
offsetsY: 12,
|
||||
rowHeights: rowHeightAccumulation,
|
||||
startCol: startColumn,
|
||||
startRow,
|
||||
});
|
||||
|
||||
// 绘制分隔线
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 验证骨架和必要属性是否有效
|
||||
private isValidSkeleton(skeleton: SpreadsheetSkeleton): boolean {
|
||||
// 如果骨架信息不存在,则退出绘制
|
||||
if (!skeleton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 限制扩展仅在指定的工作表上生效
|
||||
return skeleton?.worksheet?.getSheetId() === this._sheetId;
|
||||
}
|
||||
|
||||
// 渲染箭头
|
||||
private renderArrows(props: RenderArrowsProps) {
|
||||
const { arrow, cells, colWidths, ctx, endCol, endRow, offsetsX, offsetsY = 0, rowHeights, startCol, startRow } = props ?? {};
|
||||
|
||||
for (const { col, row } of cells) {
|
||||
if (row >= startRow - 1 && row <= endRow && col >= startCol - 1 && col <= endCol) {
|
||||
const rowStart = rowHeights[row - 1] || 0;
|
||||
const colStart = colWidths[col - 1] || 0;
|
||||
|
||||
const colEnd = colWidths[col] || colWidths[col - 1] || 0;
|
||||
const cellWidth = colEnd - colStart;
|
||||
|
||||
const xPosition = offsetsX === undefined ? colStart + cellWidth / 2 - 6 : colStart + offsetsX;
|
||||
const yPosition = rowStart + offsetsY;
|
||||
|
||||
ctx.fillText(arrow, xPosition, yPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 Canvas 的绘图属性
|
||||
private setupCanvas(ctx: UniverRenderingContext) {
|
||||
// 设置背景色为浅灰色
|
||||
ctx.fillStyle = getColor([255, 255, 255]);
|
||||
// 设置文本水平对齐方式
|
||||
ctx.textAlign = 'left';
|
||||
// 设置文本垂直对齐方式
|
||||
ctx.textBaseline = 'top';
|
||||
// 设置文本颜色为黑色
|
||||
ctx.fillStyle = getColor([24, 144, 255])!;
|
||||
// 开始绘制路径
|
||||
ctx.beginPath();
|
||||
// 设置线宽为 1
|
||||
ctx.lineWidth = 1;
|
||||
// 防止边界模糊的偏移值,使绘制的边界在高分辨率屏幕下更清晰
|
||||
ctx.translateWithPrecisionRatio(FIX_ONE_PIXEL_BLUR_OFFSET, FIX_ONE_PIXEL_BLUR_OFFSET);
|
||||
// 设置分隔线颜色为浅灰色
|
||||
ctx.strokeStyle = getColor([217, 217, 217]);
|
||||
// 设置字体样式
|
||||
ctx.font = `10px ${DEFAULT_FONTFACE_PLANE}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 定义清理扩展类,用于覆盖并清除扩展效果
|
||||
export class ClearSheetRelationCellExtension extends SheetExtension {
|
||||
override uKey: string;
|
||||
private readonly _sheetId: string; // 用于存储动态传入的 SheetId
|
||||
|
||||
constructor(props: ClearSheetRelationCellProps) {
|
||||
super();
|
||||
|
||||
const { key, sheetId } = props ?? {};
|
||||
this.uKey = key;
|
||||
this._sheetId = sheetId; // 将传入的 SheetId 存储到类中
|
||||
}
|
||||
|
||||
// 空的绘制方法,不进行任何绘制操作
|
||||
override draw(_ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
|
||||
if (skeleton?.worksheet?.getSheetId() !== this._sheetId) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { ZqSheetsCellImageService } from '../services/sheet-cell-image.service';
|
||||
|
||||
export class JnpfFacadeSheetsCellImage {
|
||||
constructor(private readonly _jnpfSheetsCellImageService: ZqSheetsCellImageService) {}
|
||||
|
||||
insertCellImage(file: File, row: number, col: number, cellData: any) {
|
||||
return this._jnpfSheetsCellImageService?.insertCellImage(file, row, col, cellData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { ICellData } from '@univerjs/core';
|
||||
|
||||
import { ZqSheetsCellService } from '../services/sheet-cell.service';
|
||||
|
||||
export class JnpfFacadeSheetsCell {
|
||||
constructor(private readonly _jnpfSheetsCellService: ZqSheetsCellService) {}
|
||||
|
||||
getAllCellData() {
|
||||
return this._jnpfSheetsCellService?.getAllCellData();
|
||||
}
|
||||
|
||||
getCellData(col: number, row: number) {
|
||||
return this._jnpfSheetsCellService?.getCellData(col, row);
|
||||
}
|
||||
|
||||
getCurrentSheetCellsCustom() {
|
||||
return this._jnpfSheetsCellService?.getCurrentSheetCellsCustom();
|
||||
}
|
||||
|
||||
getTargetCellSize(col: number, row: number) {
|
||||
return this._jnpfSheetsCellService?.getTargetCellSize(col, row);
|
||||
}
|
||||
|
||||
refreshRangeCellsView(range: any) {
|
||||
this._jnpfSheetsCellService?.refreshRangeCellsView(range);
|
||||
}
|
||||
|
||||
setCellData(col: number, row: number, cellData: ICellData) {
|
||||
this._jnpfSheetsCellService?.setCellData(col, row, cellData);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ZqSheetsFloatDomService } from '../services/sheet-float-dom.service';
|
||||
|
||||
export class JnpfFacadeSheetsFloatDom {
|
||||
constructor(private readonly _jnpfSheetsFloatDomService: ZqSheetsFloatDomService) {}
|
||||
|
||||
saveFloatEchartItems(data: any) {
|
||||
this._jnpfSheetsFloatDomService.saveFloatEchartItems(data);
|
||||
}
|
||||
|
||||
saveFloatImageItems(data: any) {
|
||||
this._jnpfSheetsFloatDomService.saveFloatImageItems(data);
|
||||
}
|
||||
|
||||
savePiniaStoreId(value: any) {
|
||||
this._jnpfSheetsFloatDomService.savePiniaStoreId(value);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { ZqSheetsFloatEchartService } from '../services/sheet-float-echart.service';
|
||||
|
||||
export class JnpfFacadeSheetsFloatEchart {
|
||||
constructor(private readonly _jnpfSheetsFloatEchartService: ZqSheetsFloatEchartService) {}
|
||||
|
||||
clearFocusDrawingId() {
|
||||
this._jnpfSheetsFloatEchartService.clearFocusDrawingId();
|
||||
}
|
||||
|
||||
savePiniaStoreId(value: any) {
|
||||
this._jnpfSheetsFloatEchartService.savePiniaStoreId(value);
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { ZqSheetsFloatImageService } from '../services/sheet-float-image.service';
|
||||
import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
|
||||
|
||||
export class JnpfFacadeSheetsFloatImage {
|
||||
constructor(
|
||||
private readonly _jnpfSheetsFloatImageService: ZqSheetsFloatImageService,
|
||||
private readonly _jnpfSheetImageIoImplService: JnpfSheetImageIoImplService,
|
||||
) {}
|
||||
|
||||
cacheImages() {
|
||||
this._jnpfSheetImageIoImplService.cacheImages();
|
||||
}
|
||||
|
||||
clearFocusDrawingId() {
|
||||
this._jnpfSheetsFloatImageService.clearFocusDrawingId();
|
||||
}
|
||||
|
||||
savePiniaStoreId(value: any) {
|
||||
this._jnpfSheetsFloatImageService.savePiniaStoreId(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ZqSheetsPrintService } from '../services/sheet-print.service';
|
||||
|
||||
export class JnpfFacadeSheetsPrint {
|
||||
constructor(private readonly _jnpfSheetsPrintService: ZqSheetsPrintService) {}
|
||||
|
||||
getLayouts(config: any) {
|
||||
return this._jnpfSheetsPrintService.getLayouts(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ZqSheetsRangeService } from '../services/sheet-range.service';
|
||||
|
||||
export class JnpfFacadeSheetsRange {
|
||||
constructor(private readonly _jnpfSheetsRangeService: ZqSheetsRangeService) {}
|
||||
|
||||
recoveryRange(row: number, col: number) {
|
||||
return this._jnpfSheetsRangeService?.recoveryRange(row, col);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { ICommandService, IImageIoService, Injector, IUniverInstanceService, LifecycleService, Univer } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
|
||||
import { JnpfFacadeSheetsCell } from '../facade/f-sheet-cell';
|
||||
import { JnpfFacadeSheetsCellImage } from '../facade/f-sheet-cell-image';
|
||||
import { JnpfFacadeSheetsFloatDom } from '../facade/f-sheet-float-dom';
|
||||
import { JnpfFacadeSheetsPrint } from '../facade/f-sheet-print';
|
||||
import { JnpfFacadeSheetsRange } from '../facade/f-sheet-range';
|
||||
import { ZqSheetsCellImageService } from '../services/sheet-cell-image.service';
|
||||
import { ZqSheetsCellService } from '../services/sheet-cell.service';
|
||||
import { ZqSheetsFloatDomService } from '../services/sheet-float-dom.service';
|
||||
import { ZqSheetsFloatEchartService } from '../services/sheet-float-echart.service';
|
||||
import { ZqSheetsFloatImageService } from '../services/sheet-float-image.service';
|
||||
import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
|
||||
import { ZqSheetsPrintService } from '../services/sheet-print.service';
|
||||
import { ZqSheetsRangeService } from '../services/sheet-range.service';
|
||||
import { JnpfFacadeSheetsFloatEchart } from './f-sheet-float-echart';
|
||||
import { JnpfFacadeSheetsFloatImage } from './f-sheet-float-image';
|
||||
|
||||
import '@univerjs/sheets/facade';
|
||||
import '@univerjs/ui/facade';
|
||||
import '@univerjs/docs-ui/facade';
|
||||
import '@univerjs/sheets-ui/facade';
|
||||
import '@univerjs/sheets-filter/facade';
|
||||
import '@univerjs/engine-formula/facade';
|
||||
import '@univerjs/sheets-formula/facade';
|
||||
import '@univerjs/sheets-data-validation/facade';
|
||||
import '@univerjs/sheets-hyper-link/facade';
|
||||
import '@univerjs/sheets-hyper-link-ui/facade';
|
||||
import '@univerjs/watermark/facade';
|
||||
import '@univerjs/sheets-thread-comment/facade';
|
||||
import '@univerjs/sheets-crosshair-highlight/facade';
|
||||
|
||||
export class ZqFUniver extends FUniver {
|
||||
constructor(injector: Injector, commandService: ICommandService, instanceService: IUniverInstanceService) {
|
||||
const lifecycleService = injector.get<LifecycleService>(LifecycleService);
|
||||
if (!lifecycleService) {
|
||||
throw new Error('LifecycleService 未正确注入');
|
||||
}
|
||||
|
||||
super(injector, commandService, instanceService, lifecycleService);
|
||||
}
|
||||
|
||||
static newAPI(wrapped: Injector | Univer): ZqFUniver {
|
||||
const injector = wrapped instanceof Univer ? wrapped.__getInjector() : wrapped;
|
||||
const commandService = injector.get<ICommandService>(ICommandService);
|
||||
const instanceService = injector.get<IUniverInstanceService>(IUniverInstanceService);
|
||||
|
||||
if (!commandService || !instanceService) {
|
||||
throw new Error('注入ICommandService或IUniverInstanceService发生错误');
|
||||
}
|
||||
|
||||
return new ZqFUniver(injector, commandService, instanceService);
|
||||
}
|
||||
|
||||
getInjector() {
|
||||
return this._injector;
|
||||
}
|
||||
|
||||
getSheetsCell(): JnpfFacadeSheetsCell | null {
|
||||
const jnpfSheetsCellService = this._injector.get(ZqSheetsCellService);
|
||||
|
||||
if (!jnpfSheetsCellService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsCell, jnpfSheetsCellService);
|
||||
}
|
||||
|
||||
getSheetsCellImage(): JnpfFacadeSheetsCellImage | null {
|
||||
const jnpfSheetsCellImageService = this._injector.get(ZqSheetsCellImageService);
|
||||
|
||||
if (!jnpfSheetsCellImageService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsCellImage, jnpfSheetsCellImageService);
|
||||
}
|
||||
|
||||
getSheetsFloatDom(): JnpfFacadeSheetsFloatDom | null {
|
||||
const jnpfSheetsFloatDomService = this._injector.get(ZqSheetsFloatDomService);
|
||||
|
||||
if (!jnpfSheetsFloatDomService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsFloatDom, jnpfSheetsFloatDomService);
|
||||
}
|
||||
|
||||
getSheetsFloatEchart(): JnpfFacadeSheetsFloatEchart | null {
|
||||
const jnpfSheetsFloatEchartService = this._injector.get(ZqSheetsFloatEchartService);
|
||||
|
||||
if (!jnpfSheetsFloatEchartService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsFloatEchart, jnpfSheetsFloatEchartService);
|
||||
}
|
||||
|
||||
getSheetsFloatImage(): JnpfFacadeSheetsFloatImage | null {
|
||||
const jnpfSheetsFloatImageService = this._injector.get(ZqSheetsFloatImageService);
|
||||
const jnpfSheetImageIoImplService = this._injector.get(IImageIoService);
|
||||
if (!jnpfSheetImageIoImplService) {
|
||||
return null;
|
||||
}
|
||||
if (!jnpfSheetsFloatImageService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsFloatImage, jnpfSheetsFloatImageService, jnpfSheetImageIoImplService as JnpfSheetImageIoImplService);
|
||||
}
|
||||
|
||||
getSheetsPrint(): JnpfFacadeSheetsPrint | null {
|
||||
const jnpfSheetsPrintService = this._injector.get(ZqSheetsPrintService);
|
||||
|
||||
if (!jnpfSheetsPrintService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsPrint, jnpfSheetsPrintService);
|
||||
}
|
||||
|
||||
getSheetsRange(): JnpfFacadeSheetsRange | null {
|
||||
const jnpfSheetsRangeService = this._injector.get(ZqSheetsRangeService);
|
||||
|
||||
if (!jnpfSheetsRangeService) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._injector.createInstance(JnpfFacadeSheetsRange, jnpfSheetsRangeService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
jnpfSheetDownloadExcelFileMenu: {
|
||||
title: 'Export Excel',
|
||||
tooltip: 'Export Excel',
|
||||
},
|
||||
jnpfSheetExcelFileMenu: {
|
||||
tooltip: 'Excel file',
|
||||
},
|
||||
jnpfSheetFloatEchartMenu: {
|
||||
tooltip: 'Charts',
|
||||
},
|
||||
jnpfSheetImportCsvFileMenu: {
|
||||
title: 'Import CSV',
|
||||
tooltip: 'Import CSV',
|
||||
},
|
||||
jnpfSheetImportExcelFileMenu: {
|
||||
title: 'Import Excel',
|
||||
tooltip: 'Import Excel',
|
||||
},
|
||||
jnpfSheetInsertFloatBarEchartMenu: {
|
||||
title: 'Bar chart',
|
||||
tooltip: 'Bar chart',
|
||||
},
|
||||
jnpfSheetInsertFloatImageMenu: {
|
||||
title: 'Floating image',
|
||||
tooltip: 'Floating image',
|
||||
},
|
||||
jnpfSheetInsertFloatLineEchartMenu: {
|
||||
title: 'Line chart',
|
||||
tooltip: 'Line chart',
|
||||
},
|
||||
jnpfSheetInsertFloatPieEchartMenu: {
|
||||
title: 'Pie chart',
|
||||
tooltip: 'Pie chart',
|
||||
},
|
||||
jnpfSheetInsertFloatRadarEchartMenu: {
|
||||
title: 'Radar chart',
|
||||
tooltip: 'Radar chart',
|
||||
},
|
||||
jnpfSheetPreviewMenu: {
|
||||
title: 'Preview',
|
||||
tooltip: 'Design preview',
|
||||
},
|
||||
jnpfSheetPrintMenu: {
|
||||
title: 'Print',
|
||||
tooltip: 'Print',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { LocaleService } from '@univerjs/core';
|
||||
|
||||
import enUS from './en-US';
|
||||
import zhCN from './zh-CN';
|
||||
import zhTW from './zh-TW';
|
||||
|
||||
/** 注册报表设计器自定义菜单文案(简中 / 英文 / 繁中) */
|
||||
export function registerZqSheetLocales(localeService: LocaleService) {
|
||||
localeService.load({
|
||||
zhCN,
|
||||
enUS,
|
||||
zhTW,
|
||||
});
|
||||
}
|
||||
|
||||
export type ZqAppLocale = 'zh-CN' | 'en-US' | 'zh-TW';
|
||||
|
||||
/** 将应用 locale 映射为插件 LocaleService 自定义文案 key */
|
||||
export function mapAppLocaleToZqPackKey(appLocale?: string): 'enUS' | 'zhCN' | 'zhTW' {
|
||||
if (appLocale === 'en-US') return 'enUS';
|
||||
if (appLocale === 'zh-TW') return 'zhTW';
|
||||
return 'zhCN';
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
jnpfSheetDownloadExcelFileMenu: {
|
||||
title: '导出Excel',
|
||||
tooltip: '导出Excel',
|
||||
},
|
||||
jnpfSheetExcelFileMenu: {
|
||||
tooltip: 'Excel文件',
|
||||
},
|
||||
jnpfSheetFloatEchartMenu: {
|
||||
tooltip: '图表',
|
||||
},
|
||||
jnpfSheetImportCsvFileMenu: {
|
||||
title: '导入CSV',
|
||||
tooltip: '导入CSV',
|
||||
},
|
||||
jnpfSheetImportExcelFileMenu: {
|
||||
title: '导入Excel',
|
||||
tooltip: '导入Excel',
|
||||
},
|
||||
jnpfSheetInsertFloatBarEchartMenu: {
|
||||
title: '柱状图',
|
||||
tooltip: '柱状图-图表',
|
||||
},
|
||||
jnpfSheetInsertFloatImageMenu: {
|
||||
title: '自定义悬浮图片',
|
||||
tooltip: '自定义悬浮图片-图片',
|
||||
},
|
||||
jnpfSheetInsertFloatLineEchartMenu: {
|
||||
title: '折线图',
|
||||
tooltip: '折线图-图表',
|
||||
},
|
||||
jnpfSheetInsertFloatPieEchartMenu: {
|
||||
title: '饼图',
|
||||
tooltip: '饼图-图表',
|
||||
},
|
||||
jnpfSheetInsertFloatRadarEchartMenu: {
|
||||
title: '雷达图',
|
||||
tooltip: '雷达图-图表',
|
||||
},
|
||||
jnpfSheetPreviewMenu: {
|
||||
title: '设计预览功能',
|
||||
tooltip: '设计预览',
|
||||
},
|
||||
jnpfSheetPrintMenu: {
|
||||
title: '打印功能',
|
||||
tooltip: '打印',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
jnpfSheetDownloadExcelFileMenu: {
|
||||
title: '匯出 Excel',
|
||||
tooltip: '匯出 Excel',
|
||||
},
|
||||
jnpfSheetExcelFileMenu: {
|
||||
tooltip: 'Excel 檔案',
|
||||
},
|
||||
jnpfSheetFloatEchartMenu: {
|
||||
tooltip: '圖表',
|
||||
},
|
||||
jnpfSheetImportCsvFileMenu: {
|
||||
title: '匯入 CSV',
|
||||
tooltip: '匯入 CSV',
|
||||
},
|
||||
jnpfSheetImportExcelFileMenu: {
|
||||
title: '匯入 Excel',
|
||||
tooltip: '匯入 Excel',
|
||||
},
|
||||
jnpfSheetInsertFloatBarEchartMenu: {
|
||||
title: '柱狀圖',
|
||||
tooltip: '柱狀圖',
|
||||
},
|
||||
jnpfSheetInsertFloatImageMenu: {
|
||||
title: '自訂懸浮圖片',
|
||||
tooltip: '自訂懸浮圖片',
|
||||
},
|
||||
jnpfSheetInsertFloatLineEchartMenu: {
|
||||
title: '折線圖',
|
||||
tooltip: '折線圖',
|
||||
},
|
||||
jnpfSheetInsertFloatPieEchartMenu: {
|
||||
title: '圓餅圖',
|
||||
tooltip: '圓餅圖',
|
||||
},
|
||||
jnpfSheetInsertFloatRadarEchartMenu: {
|
||||
title: '雷達圖',
|
||||
tooltip: '雷達圖',
|
||||
},
|
||||
jnpfSheetPreviewMenu: {
|
||||
title: '設計預覽',
|
||||
tooltip: '設計預覽',
|
||||
},
|
||||
jnpfSheetPrintMenu: {
|
||||
title: '列印',
|
||||
tooltip: '列印',
|
||||
},
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsCellImageService } from '../services/sheet-cell-image.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsCellImagePlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_CELL_IMAGE_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsCellImageService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsCellService } from '../services/sheet-cell.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsCellPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_CELL_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsCellService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { ZqSheetsDialogController } from '../controllers/sheet-dialog.controller';
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsDialogPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_DIALOG_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsDialogController]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
|
||||
this._injector.get(ZqSheetsDialogController);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { ZqSheetsExcelFileController } from '../controllers/sheet-excel-file.controller';
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsExcelFileService } from '../services/sheet-excel-file.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsExcelFilePlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_EXCEL_FILE_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsExcelFileController], [ZqSheetsExcelFileService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
|
||||
this._injector.get(ZqSheetsExcelFileController);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsFloatDomService } from '../services/sheet-float-dom.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsFloatDomPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_FLOAT_DOM_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsFloatDomService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { ZqSheetsFloatEchartController } from '../controllers/sheet-float-echart.controller';
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsFloatEchartService } from '../services/sheet-float-echart.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsFloatEchartPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_FLOAT_ECHART_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsFloatEchartController], [ZqSheetsFloatEchartService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
|
||||
this._injector.get(ZqSheetsFloatEchartController);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, IImageIoService, Inject, Injector, LocaleService, mergeOverrideWithDependencies, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { ZqSheetsFloatImageController } from '../controllers/sheet-float-image.controller';
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsFloatImageService } from '../services/sheet-float-image.service';
|
||||
import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsFloatImagePlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_FLOAT_IMAGE_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
mergeOverrideWithDependencies(
|
||||
[[ZqSheetsFloatImageController], [ZqSheetsFloatImageService], [IImageIoService, { useClass: JnpfSheetImageIoImplService }]] as Dependency[],
|
||||
[],
|
||||
).forEach(d => this._injector.add(d));
|
||||
|
||||
this._injector.get(ZqSheetsFloatImageController);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { ZqSheetsPreviewController } from '../controllers/sheet-preview.controller';
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsPreviewPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_PREVIEW_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsPreviewController]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
|
||||
this._injector.get(ZqSheetsPreviewController);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsPrintUiService } from '../services/sheet-print-ui.service';
|
||||
import { ZqSheetsPrintService } from '../services/sheet-print.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsPrintPlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_PRINT_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsPrintService], [ZqSheetsPrintUiService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
|
||||
import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
|
||||
import { registerZqSheetLocales } from '../locales/register';
|
||||
import { ZqSheetsRangeService } from '../services/sheet-range.service';
|
||||
|
||||
@DependentOn(UniverSheetsUIPlugin)
|
||||
export class ZqSheetsRangePlugin extends Plugin {
|
||||
static override pluginName = 'JNPF_SHEET_RANGE_PLUGIN';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
) {
|
||||
super();
|
||||
|
||||
registerZqSheetLocales(this._localeService);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([[ZqSheetsRangeService]] as Dependency[]).forEach(d => this._injector.add(d));
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
import type { IAccessor, IImageIoServiceParam, Nullable } from '@univerjs/core';
|
||||
import {
|
||||
BooleanNumber,
|
||||
BuildTextUtils,
|
||||
createDocumentModelWithStyle,
|
||||
Disposable,
|
||||
DrawingTypeEnum,
|
||||
ICommandService,
|
||||
IImageIoService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUniverInstanceService,
|
||||
ObjectRelativeFromH,
|
||||
ObjectRelativeFromV,
|
||||
PositionedObjectLayoutType,
|
||||
UniverInstanceType,
|
||||
Workbook,
|
||||
WrapTextType,
|
||||
} from '@univerjs/core';
|
||||
import { docDrawingPositionToTransform } from '@univerjs/docs-ui';
|
||||
import { getImageSize } from '@univerjs/drawing';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import type { ISheetLocationBase } from '@univerjs/sheets';
|
||||
import { SetRangeValuesCommand } from '@univerjs/sheets';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
import { rotatedBoundingBox } from '../utils';
|
||||
|
||||
export class ZqSheetsCellImageService extends Disposable {
|
||||
constructor(
|
||||
@Inject(Injector) private readonly _injector: Injector,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IImageIoService private readonly _imageIoService: IImageIoService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前选中的单元格插入图片
|
||||
* @param file 要插入的图片文件
|
||||
* @param row 行
|
||||
* @param col 列
|
||||
* @param cellData 单元格配置信息
|
||||
* @returns 插入结果,成功返回执行命令的结果,失败返回 `false` 或 `null`
|
||||
*/
|
||||
public async insertCellImage(file: File, row: number, col: number, cellData: any) {
|
||||
// 获取当前的工作簿实例
|
||||
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) {
|
||||
return false;
|
||||
}
|
||||
const unitId = workbook.getUnitId();
|
||||
|
||||
// 获取当前激活的工作表
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return false;
|
||||
}
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
|
||||
// 将图片文件保存到 ImageIoService 并获取图片参数
|
||||
let imageParam: Nullable<IImageIoServiceParam>;
|
||||
try {
|
||||
imageParam = await this._imageIoService.saveImage(file);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (imageParam == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取图片相关信息
|
||||
const { base64Cache, imageId, imageSourceType, source } = imageParam;
|
||||
const { height, image, width } = await getImageSize(base64Cache || '');
|
||||
// 缓存图片数据
|
||||
this._imageIoService.addImageSourceCache(source, imageSourceType, image);
|
||||
|
||||
const docDataModel = createDocumentModelWithStyle('', {});
|
||||
|
||||
// 计算图片适应单元格后的尺寸
|
||||
const imageSize = this._getDrawingSizeByCell(
|
||||
this._injector,
|
||||
{
|
||||
col,
|
||||
row,
|
||||
subUnitId,
|
||||
unitId,
|
||||
},
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
);
|
||||
if (!imageSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 定义图片的文档变换参数
|
||||
const docTransform = {
|
||||
angle: 0,
|
||||
positionH: {
|
||||
posOffset: 0,
|
||||
relativeFrom: ObjectRelativeFromH.PAGE,
|
||||
},
|
||||
positionV: {
|
||||
posOffset: 0,
|
||||
relativeFrom: ObjectRelativeFromV.PARAGRAPH,
|
||||
},
|
||||
size: {
|
||||
height: imageSize.height,
|
||||
width: imageSize.width,
|
||||
},
|
||||
};
|
||||
|
||||
// 定义文档中的绘图参数
|
||||
const docDrawingParam = {
|
||||
behindDoc: BooleanNumber.FALSE,
|
||||
description: '',
|
||||
distB: 0,
|
||||
distL: 0,
|
||||
distR: 0,
|
||||
distT: 0,
|
||||
docTransform,
|
||||
drawingId: imageId,
|
||||
drawingType: DrawingTypeEnum.DRAWING_IMAGE,
|
||||
imageSourceType,
|
||||
layoutType: PositionedObjectLayoutType.INLINE, // Insert inline drawing by default.
|
||||
source,
|
||||
subUnitId: docDataModel.getUnitId(),
|
||||
title: '',
|
||||
transform: docDrawingPositionToTransform(docTransform),
|
||||
unitId: docDataModel.getUnitId(),
|
||||
wrapText: WrapTextType.BOTH_SIDES,
|
||||
};
|
||||
|
||||
// 生成插入绘图的 JSON 结构
|
||||
const jsonXActions = BuildTextUtils.drawing.add({
|
||||
documentDataModel: docDataModel,
|
||||
drawings: [docDrawingParam],
|
||||
selection: {
|
||||
collapsed: true,
|
||||
endOffset: 0,
|
||||
startOffset: 0,
|
||||
},
|
||||
});
|
||||
|
||||
if (!jsonXActions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
docDataModel.apply(jsonXActions);
|
||||
|
||||
return this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, {
|
||||
value: {
|
||||
[row]: {
|
||||
[col]: {
|
||||
custom: {
|
||||
...cellData.custom,
|
||||
drawingId: imageId,
|
||||
},
|
||||
p: docDataModel.getSnapshot(),
|
||||
t: 1,
|
||||
v: cellData.v ?? '',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格内图形的大小
|
||||
* @param accessor 访问器对象
|
||||
* @param location 单元格位置信息
|
||||
* @param originImageWidth 原始图像宽度
|
||||
* @param originImageHeight 原始图像高度
|
||||
* @param angle 旋转角度(0-360,单位:度)
|
||||
* @returns 计算后的图像尺寸,或在获取失败时返回 `false`
|
||||
*/
|
||||
private _getDrawingSizeByCell(accessor: IAccessor, location: ISheetLocationBase, originImageWidth: number, originImageHeight: number, angle: number) {
|
||||
const { rotatedHeight, rotatedWidth } = rotatedBoundingBox(originImageWidth, originImageHeight, angle);
|
||||
const renderManagerService = accessor.get(IRenderManagerService);
|
||||
const currentRender = renderManagerService.getRenderById(location.unitId);
|
||||
if (!currentRender) {
|
||||
return false;
|
||||
}
|
||||
const skeletonManagerService = currentRender.with(SheetSkeletonManagerService);
|
||||
const skeleton = skeletonManagerService.getWorksheetSkeleton(location.subUnitId)?.skeleton;
|
||||
if (skeleton == null) {
|
||||
return false;
|
||||
}
|
||||
const cellInfo = skeleton.getCellWithCoordByIndex(location.row, location.col);
|
||||
|
||||
const cellWidth = cellInfo.mergeInfo.endX - cellInfo.mergeInfo.startX - 2;
|
||||
const cellHeight = cellInfo.mergeInfo.endY - cellInfo.mergeInfo.startY - 2;
|
||||
const imageRatio = rotatedWidth / rotatedHeight;
|
||||
const imageWidth = Math.ceil(Math.min(cellWidth, cellHeight * imageRatio));
|
||||
const scale = imageWidth / rotatedWidth;
|
||||
const realScale = !scale || Number.isNaN(scale) ? 0.001 : scale;
|
||||
|
||||
return {
|
||||
height: originImageHeight * realScale,
|
||||
width: originImageWidth * realScale,
|
||||
};
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import type { ICellData } from '@univerjs/core';
|
||||
import { Disposable, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
export class ZqSheetsCellService extends Disposable {
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
// 获取所有单元格数据
|
||||
public getAllCellData() {
|
||||
const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
|
||||
return sheet.getCellMatrix().getMatrix();
|
||||
}
|
||||
|
||||
// 获取指定单元格数据
|
||||
public getCellData(col: number, row: number) {
|
||||
const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
|
||||
const cellMatrix = sheet.getCellMatrix().getMatrix();
|
||||
|
||||
return cellMatrix?.[row]?.[col] ?? null; // 如果未找到匹配的单元格,返回 null
|
||||
}
|
||||
|
||||
// 获取当前单元格的自定义属性
|
||||
public getCurrentSheetCellsCustom() {
|
||||
const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
|
||||
const cellMatrix = sheet?.getCellMatrix()?.getMatrix() ?? {};
|
||||
|
||||
const result: { [row: string]: { [col: string]: any } } = {};
|
||||
Object.entries(cellMatrix)?.forEach(([row, colObj]) => {
|
||||
if (colObj && typeof colObj === 'object') {
|
||||
const rowData: { [col: string]: any } = {};
|
||||
|
||||
Object.entries(colObj)?.forEach(([col, value]: any) => {
|
||||
if (value?.custom) {
|
||||
rowData[col] = { ...value };
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(rowData)?.length) {
|
||||
result[row] = rowData;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 获取激活单元格的尺寸信息
|
||||
public getTargetCellSize(col: number, row: number): null | { cellHeight: number; cellWidth: number } {
|
||||
// 获取当前的工作簿实例
|
||||
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!workbook) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unitId = workbook.getUnitId();
|
||||
|
||||
// 获取当前激活的工作表
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
|
||||
// 获取对应的骨架渲染服务
|
||||
const skeleton = this._renderManagerService?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getUnitSkeleton(unitId, subUnitId)?.skeleton as any;
|
||||
|
||||
if (!skeleton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取指定单元格的位置信息
|
||||
const cellInfo = skeleton.getCellWithCoordByIndex(row, col);
|
||||
if (!cellInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解构单元格信息
|
||||
const { endX, endY, isMergedMainCell, mergeInfo, startX, startY } = cellInfo;
|
||||
|
||||
let cellWidth: number;
|
||||
let cellHeight: number;
|
||||
|
||||
if (isMergedMainCell && mergeInfo) {
|
||||
cellWidth = mergeInfo.endX - mergeInfo.startX;
|
||||
cellHeight = mergeInfo.endY - mergeInfo.startY;
|
||||
} else {
|
||||
cellWidth = endX - startX;
|
||||
cellHeight = endY - startY;
|
||||
}
|
||||
|
||||
return {
|
||||
cellHeight: Math.floor(cellHeight) - 2,
|
||||
cellWidth: Math.floor(cellWidth) - 2,
|
||||
};
|
||||
}
|
||||
|
||||
// 刷新所有单元格的视图
|
||||
public refreshRangeCellsView(range: any) {
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalValue = range?.getValue() ?? '';
|
||||
range?.setValue(originalValue);
|
||||
}
|
||||
|
||||
// 设置单元格数据
|
||||
public setCellData(col: number, row: number, cellData: ICellData) {
|
||||
const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
|
||||
const cellMatrix = sheet.getCellMatrix();
|
||||
|
||||
try {
|
||||
cellMatrix.setValue(row, col, cellData);
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
import type { ICellData } from '@univerjs/core';
|
||||
import { Disposable, ICommandService, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
|
||||
import { SetRangeValuesCommand } from '@univerjs/sheets';
|
||||
|
||||
import { ZQ_REPORT_IMPORT_EXCEL_URL } from '../utils/define';
|
||||
|
||||
export class ZqSheetsExcelFileService extends Disposable {
|
||||
private _exportExcelHandler: (() => void) | null = null;
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
setExportExcelHandler(handler: (() => void) | null) {
|
||||
this._exportExcelHandler = handler;
|
||||
}
|
||||
|
||||
handleDownloadExcel() {
|
||||
this._exportExcelHandler?.();
|
||||
}
|
||||
|
||||
public handleImportCsv() {
|
||||
const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
|
||||
|
||||
this._waitUserSelectSpreadsheetFile(({ colsCount, data, rowsCount }) => {
|
||||
// 设置工作表大小
|
||||
sheet.setColumnCount(colsCount);
|
||||
sheet.setRowCount(rowsCount);
|
||||
|
||||
// 设置工作表数据
|
||||
this._commandService
|
||||
.executeCommand(SetRangeValuesCommand.id, {
|
||||
range: {
|
||||
endColumn: colsCount - 1, // 结束列索引
|
||||
endRow: rowsCount - 1, // 结束行索引
|
||||
startColumn: 0, // 起始列索引
|
||||
startRow: 0, // 起始行索引
|
||||
},
|
||||
value: this._parseCSV2UniverData(data),
|
||||
})
|
||||
.then();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 CSV 解析为 Univer 数据
|
||||
* @param csv CSV 数据
|
||||
* @returns { v: string }[][] 返回解析后的数据
|
||||
*/
|
||||
private _parseCSV2UniverData(csv: string[][]): ICellData[][] {
|
||||
return csv.map(row => {
|
||||
return row.map(cell => {
|
||||
return {
|
||||
v: cell || '',
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _applyGridToSheet(
|
||||
sheet: any,
|
||||
payload: { colsCount: number; data: ICellData[][]; rowsCount: number },
|
||||
) {
|
||||
const { colsCount, data, rowsCount } = payload;
|
||||
sheet.setColumnCount(colsCount);
|
||||
sheet.setRowCount(rowsCount);
|
||||
this._commandService
|
||||
.executeCommand(SetRangeValuesCommand.id, {
|
||||
range: {
|
||||
endColumn: colsCount - 1,
|
||||
endRow: rowsCount - 1,
|
||||
startColumn: 0,
|
||||
startRow: 0,
|
||||
},
|
||||
value: data,
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待用户选择 CSV / Excel 文件
|
||||
*/
|
||||
private _waitUserSelectSpreadsheetFile(
|
||||
onSelect: (data: { colsCount: number; data: ICellData[][]; rowsCount: number }) => void,
|
||||
) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.csv,.xlsx,.xls';
|
||||
input.click();
|
||||
|
||||
input.addEventListener('change', async () => {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const name = file.name.toLowerCase();
|
||||
if (name.endsWith('.xlsx') || name.endsWith('.xls')) {
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const resp = await fetch(ZQ_REPORT_IMPORT_EXCEL_URL, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.error('Excel import failed', await resp.text());
|
||||
return;
|
||||
}
|
||||
const json = await resp.json();
|
||||
const body = json?.data !== undefined && json?.code !== undefined ? json.data : json;
|
||||
const gridData = body as {
|
||||
rowsCount?: number;
|
||||
colsCount?: number;
|
||||
data?: ICellData[][];
|
||||
};
|
||||
onSelect({
|
||||
colsCount: gridData.colsCount || 0,
|
||||
rowsCount: gridData.rowsCount || 0,
|
||||
data: gridData.data || [],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Excel import error', err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
const text = reader.result;
|
||||
if (typeof text !== 'string') return;
|
||||
|
||||
const rows = text.split(/\r\n|\n/);
|
||||
const csv = rows.map(line => line.split(','));
|
||||
const colsCount = csv.reduce((max, row) => Math.max(max, row.length), 0);
|
||||
|
||||
onSelect({
|
||||
colsCount,
|
||||
data: this._parseCSV2UniverData(csv),
|
||||
rowsCount: csv.length,
|
||||
});
|
||||
});
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { Disposable } from '@univerjs/core';
|
||||
|
||||
export class ZqSheetsFloatDomService extends Disposable {
|
||||
// private _floatEchartItems: Record<string, any> = {};
|
||||
//
|
||||
// private _floatImageItems: Record<string, any> = {};
|
||||
// private _piniaStoreId: null | string = null;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// this._addFloatDomHook();
|
||||
}
|
||||
|
||||
// 存入图表列表数据
|
||||
public saveFloatEchartItems(data: any) {
|
||||
// this._floatEchartItems = data;
|
||||
}
|
||||
|
||||
// 存入图片列表数据
|
||||
public saveFloatImageItems(data: any) {
|
||||
// this._floatImageItems = data;
|
||||
}
|
||||
|
||||
// 存入store的id
|
||||
public savePiniaStoreId(data: any) {
|
||||
// this._piniaStoreId = data;
|
||||
}
|
||||
|
||||
// 增加Hook
|
||||
// private _addFloatDomHook() {
|
||||
// this.disposeWithMe(
|
||||
// this._sheetCanvasFloatDomManagerService.addHook({
|
||||
// onGetFloatDomProps: (id: string) => {
|
||||
// if (this._floatImageItems?.hasOwnProperty(id)) {
|
||||
// const { domId } = this._floatImageItems[id] ?? {};
|
||||
// return { id: domId, piniaStoreId: this._piniaStoreId };
|
||||
// }
|
||||
//
|
||||
// if (this._floatEchartItems?.hasOwnProperty(id)) {
|
||||
// const { domId } = this._floatEchartItems[id] ?? {};
|
||||
// return { id: domId, piniaStoreId: this._piniaStoreId };
|
||||
// }
|
||||
//
|
||||
// return {
|
||||
// id,
|
||||
// };
|
||||
// },
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import { Disposable, ICommandService, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { IDrawingManagerService } from '@univerjs/drawing';
|
||||
import { DeviceInputEventType, IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
|
||||
import { attachRangeWithCoord, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
import { DefaultFloatEchartHeight, DefaultFloatEchartOptions, DefaultFloatEchartWidth, ZqCommandIds, ZqUniverFloatEchartKey } from '../utils/define';
|
||||
import { buildUUID } from '../utils/uuid';
|
||||
|
||||
const regex = /^ZqUniverFloat(.*?)Echart$/; // 正则表达式匹配 ZqUniver 和 Echart
|
||||
|
||||
export class ZqSheetsFloatEchartService extends Disposable {
|
||||
private _focusDrawingId: null | string = null;
|
||||
private _piniaStoreId: null | string = null;
|
||||
|
||||
private readonly defaultPosition = {
|
||||
endX: DefaultFloatEchartWidth,
|
||||
endY: DefaultFloatEchartHeight,
|
||||
startX: 100,
|
||||
startY: 100,
|
||||
};
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
|
||||
@Inject(SheetCanvasFloatDomManagerService) private readonly _sheetCanvasFloatDomManagerService: SheetCanvasFloatDomManagerService,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._subscribeToFocusEvents();
|
||||
}
|
||||
|
||||
// 清空焦点对象的id
|
||||
public clearFocusDrawingId() {
|
||||
if (!this._focusDrawingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusDrawingId = null;
|
||||
}
|
||||
|
||||
// 创建并插入图表DOM
|
||||
public insertFloatEchart(componentKey: string) {
|
||||
this._commandService
|
||||
.executeCommand('sheet.operation.set-cell-edit-visible', {
|
||||
_eventType: DeviceInputEventType.PointerUp,
|
||||
visible: false,
|
||||
})
|
||||
.then();
|
||||
|
||||
const initPosition = this._calculateInitPosition();
|
||||
const domId = `echart_${buildUUID()}`;
|
||||
|
||||
const addResult = this._sheetCanvasFloatDomManagerService?.addFloatDomToPosition({
|
||||
allowTransform: true,
|
||||
componentKey: ZqUniverFloatEchartKey,
|
||||
initPosition,
|
||||
data: {
|
||||
id: domId,
|
||||
piniaStoreId: this._piniaStoreId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!addResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = componentKey?.replace(regex, '$1');
|
||||
const echartType = type ? type.charAt(0)?.toLowerCase() + type.slice(1) : undefined;
|
||||
|
||||
const newParams = {
|
||||
domId,
|
||||
drawingId: addResult.id,
|
||||
echartType,
|
||||
option: JSON.parse(JSON.stringify(DefaultFloatEchartOptions?.[echartType as keyof typeof DefaultFloatEchartOptions])),
|
||||
};
|
||||
// 自定义命令出来,告知相关的信息
|
||||
this._commandService.executeCommand(ZqCommandIds?.insertedFloatEchart, newParams).then();
|
||||
}
|
||||
|
||||
// 存入store的id
|
||||
public savePiniaStoreId(data: string) {
|
||||
this._piniaStoreId = data;
|
||||
}
|
||||
|
||||
// 计算初始位置
|
||||
private _calculateInitPosition() {
|
||||
// 获取当前单元格数据
|
||||
const unit = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!unit) {
|
||||
console.warn('警告:未找到单元。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
const unitId = unit.getUnitId();
|
||||
const currentSelections = this._selectionManagerService.getCurrentSelections();
|
||||
const firstSelection = currentSelections?.[0];
|
||||
if (!firstSelection?.range) {
|
||||
console.warn('警告:未找到选择范围。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
// 获取当前表格骨架信息
|
||||
const sheetSkeletonService = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService);
|
||||
const currentSkeleton = sheetSkeletonService?.getCurrent()?.skeleton;
|
||||
if (!currentSkeleton) {
|
||||
console.warn('警告:未找到骨架。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
// 解构表格尺寸信息
|
||||
const { columnHeaderHeight = 0, columnTotalWidth = 0, rowHeaderWidth = 0, rowTotalHeight = 0 } = currentSkeleton;
|
||||
const columnScreenTotalWidth = columnTotalWidth + rowHeaderWidth;
|
||||
const columnScreenTotalHeight = rowTotalHeight + columnHeaderHeight;
|
||||
|
||||
// 获取范围信息并计算坐标
|
||||
const rangeInfo = attachRangeWithCoord(currentSkeleton, firstSelection.range) || {
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
};
|
||||
const { startX: rangeStartX, startY: rangeStartY } = rangeInfo;
|
||||
|
||||
// 计算图表的边界坐标
|
||||
const startX = Math.min(rangeStartX + DefaultFloatEchartWidth, columnScreenTotalWidth) - DefaultFloatEchartWidth;
|
||||
const endX = Math.min(rangeStartX + DefaultFloatEchartWidth, columnScreenTotalWidth);
|
||||
const startY = Math.min(rangeStartY + DefaultFloatEchartHeight, columnScreenTotalHeight) - DefaultFloatEchartHeight;
|
||||
const endY = Math.min(rangeStartY + DefaultFloatEchartHeight, columnScreenTotalHeight);
|
||||
|
||||
return { endX, endY, startX, startY };
|
||||
}
|
||||
|
||||
// 订阅图表的焦点事件
|
||||
private _subscribeToFocusEvents() {
|
||||
this.disposeWithMe(
|
||||
this._drawingManagerService.focus$.subscribe(params => {
|
||||
if (params && params?.length) {
|
||||
const firstParam = (params?.[0] ?? {}) as any;
|
||||
const { componentKey, drawingId } = firstParam;
|
||||
|
||||
if (drawingId !== this._focusDrawingId && componentKey === ZqUniverFloatEchartKey) {
|
||||
this._focusDrawingId = drawingId;
|
||||
|
||||
this._commandService?.executeCommand(ZqCommandIds.focusFloatEchart, firstParam);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import { Disposable, ICommandService, Inject, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
|
||||
import { IDrawingManagerService } from '@univerjs/drawing';
|
||||
import { DeviceInputEventType, IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
|
||||
import { attachRangeWithCoord, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
import { DefaultFloatImageHeight, DefaultFloatImageOption, DefaultFloatImageWidth, ZqCommandIds, ZqUniverFloatImageKey } from '../utils/define';
|
||||
import { buildUUID } from '../utils/uuid';
|
||||
|
||||
export class ZqSheetsFloatImageService extends Disposable {
|
||||
private _focusDrawingId: null | string = null;
|
||||
private _piniaStoreId: null | string = null;
|
||||
|
||||
private readonly defaultPosition = { endX: DefaultFloatImageWidth, endY: DefaultFloatImageHeight, startX: 100, startY: 100 };
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
|
||||
@Inject(SheetCanvasFloatDomManagerService) private readonly _sheetCanvasFloatDomManagerService: SheetCanvasFloatDomManagerService,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._subscribeToFocusEvents();
|
||||
}
|
||||
|
||||
// 清空焦点对象的id
|
||||
public clearFocusDrawingId() {
|
||||
if (!this._focusDrawingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusDrawingId = null;
|
||||
}
|
||||
|
||||
// 创建并插入悬浮图片DOM
|
||||
public insertFloatImage() {
|
||||
this._commandService
|
||||
.executeCommand('sheet.operation.set-cell-edit-visible', {
|
||||
_eventType: DeviceInputEventType.PointerUp,
|
||||
visible: false,
|
||||
})
|
||||
.then();
|
||||
|
||||
const initPosition = this._calculateInitPosition();
|
||||
const domId = `float_image_${buildUUID()}`;
|
||||
|
||||
const addResult = this._sheetCanvasFloatDomManagerService?.addFloatDomToPosition({
|
||||
allowTransform: true,
|
||||
componentKey: ZqUniverFloatImageKey,
|
||||
initPosition,
|
||||
data: {
|
||||
id: domId,
|
||||
piniaStoreId: this._piniaStoreId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!addResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newParams = {
|
||||
domId,
|
||||
drawingId: addResult.id,
|
||||
imageType: 'BASE64',
|
||||
option: JSON.parse(JSON.stringify(DefaultFloatImageOption)),
|
||||
};
|
||||
|
||||
this._commandService.executeCommand(ZqCommandIds?.insertedFloatImage, newParams).then();
|
||||
}
|
||||
|
||||
// 存入store的id
|
||||
public savePiniaStoreId(data: string) {
|
||||
this._piniaStoreId = data;
|
||||
}
|
||||
|
||||
// 计算初始位置
|
||||
private _calculateInitPosition() {
|
||||
// 获取当前单元格数据
|
||||
const unit = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!unit) {
|
||||
console.warn('警告:未找到单元。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
const unitId = unit!.getUnitId();
|
||||
const currentSelections = this._selectionManagerService.getCurrentSelections();
|
||||
const firstSelection = currentSelections?.[0] ?? {};
|
||||
if (!firstSelection?.range) {
|
||||
console.warn('警告:未找到选择范围。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
// 获取当前表格骨架信息
|
||||
const sheetSkeletonService = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService);
|
||||
const currentSkeleton = sheetSkeletonService?.getCurrent()?.skeleton;
|
||||
if (!currentSkeleton) {
|
||||
console.warn('警告:未找到骨架。使用默认位置 (100, 100)。');
|
||||
return this.defaultPosition;
|
||||
}
|
||||
|
||||
// 解构表格尺寸信息
|
||||
const { columnHeaderHeight = 0, columnTotalWidth = 0, rowHeaderWidth = 0, rowTotalHeight = 0 } = currentSkeleton;
|
||||
const columnScreenTotalWidth = columnTotalWidth + rowHeaderWidth;
|
||||
const columnScreenTotalHeight = rowTotalHeight + columnHeaderHeight;
|
||||
|
||||
// 获取范围信息并计算坐标
|
||||
const rangeInfo = attachRangeWithCoord(currentSkeleton, firstSelection.range) || { startX: 0, startY: 0 };
|
||||
const { startX: rangeStartX, startY: rangeStartY } = rangeInfo;
|
||||
|
||||
// 计算图表的边界坐标
|
||||
const startX = Math.min(rangeStartX + DefaultFloatImageWidth, columnScreenTotalWidth) - DefaultFloatImageWidth;
|
||||
const endX = Math.min(rangeStartX + DefaultFloatImageWidth, columnScreenTotalWidth);
|
||||
const startY = Math.min(rangeStartY + DefaultFloatImageHeight, columnScreenTotalHeight) - DefaultFloatImageHeight;
|
||||
const endY = Math.min(rangeStartY + DefaultFloatImageHeight, columnScreenTotalHeight);
|
||||
|
||||
return { endX, endY, startX, startY };
|
||||
}
|
||||
|
||||
// 订阅图表的焦点事件
|
||||
private _subscribeToFocusEvents() {
|
||||
this.disposeWithMe(
|
||||
this._drawingManagerService.focus$.subscribe(params => {
|
||||
if (params && params?.length) {
|
||||
const firstParam = (params?.[0] ?? {}) as any;
|
||||
const { componentKey, drawingId } = firstParam;
|
||||
|
||||
if (drawingId !== this._focusDrawingId && componentKey === ZqUniverFloatImageKey) {
|
||||
this._focusDrawingId = drawingId;
|
||||
|
||||
this._commandService?.executeCommand(ZqCommandIds.focusFloatImage, firstParam);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import type {
|
||||
IImageIoService,
|
||||
IImageIoServiceParam,
|
||||
Nullable,
|
||||
Workbook,
|
||||
Worksheet,
|
||||
} from '@univerjs/core';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import {
|
||||
generateRandomId,
|
||||
ImageSourceType,
|
||||
ImageUploadStatusType,
|
||||
Inject,
|
||||
Injector,
|
||||
IUniverInstanceService,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export const DRAWING_IMAGE_ALLOW_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
export const DRAWING_IMAGE_ALLOW_IMAGE_LIST = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'];
|
||||
|
||||
export class JnpfSheetImageIoImplService implements IImageIoService {
|
||||
private _change$ = new Subject<number>();
|
||||
|
||||
change$ = this._change$ as Observable<number>;
|
||||
private _imageSourceCache: Map<string, HTMLImageElement> = new Map();
|
||||
|
||||
private _waitCount = 0;
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) private readonly _accessor: Injector,
|
||||
@IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService,
|
||||
) {}
|
||||
|
||||
addImageSourceCache(source: string, imageSourceType: ImageSourceType, imageSource: Nullable<HTMLImageElement>) {
|
||||
if (imageSourceType === ImageSourceType.BASE64 || imageSource == null) {
|
||||
return;
|
||||
}
|
||||
this._imageSourceCache.set(source, imageSource);
|
||||
}
|
||||
|
||||
cacheImages() {
|
||||
const sheetInfo = this._sheet();
|
||||
if (!sheetInfo) {
|
||||
return;
|
||||
}
|
||||
const { sheet, unitId, subUnitId } = sheetInfo;
|
||||
const skeleton = this._getSkeleton(unitId, subUnitId);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
skeleton.resetCache();
|
||||
sheet.getCellMatrix().forValue((_row, _col, value) => {
|
||||
if (value && value.p && value.p.drawings) {
|
||||
Object.keys(value.p.drawings).forEach(drawing => {
|
||||
const drawingItem: any = value.p?.drawings?.[drawing];
|
||||
if (value.p && value.p.drawings && drawingItem && drawingItem.imageSourceType === 'BASE64' && drawingItem.source) {
|
||||
this._imageSourceCache.set(drawingItem.imageSourceType, drawingItem.source);
|
||||
// 强制缓存
|
||||
skeleton.imageCacheMap.getImage(drawingItem.imageSourceType, drawingItem.source);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearMemory() {}
|
||||
|
||||
async getImage(imageId: string): Promise<string> {
|
||||
return imageId;
|
||||
}
|
||||
|
||||
getImageSourceCache(source: string, imageSourceType: ImageSourceType) {
|
||||
if (imageSourceType === ImageSourceType.BASE64) {
|
||||
const image = new Image();
|
||||
image.src = source;
|
||||
return image;
|
||||
}
|
||||
return this._imageSourceCache.get(source);
|
||||
}
|
||||
|
||||
async saveImage(imageFile: File): Promise<Nullable<IImageIoServiceParam>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!DRAWING_IMAGE_ALLOW_IMAGE_LIST.includes(imageFile.type)) {
|
||||
reject(new Error(ImageUploadStatusType.ERROR_IMAGE_TYPE));
|
||||
this._decreaseWaiting();
|
||||
return;
|
||||
}
|
||||
if (imageFile.size > DRAWING_IMAGE_ALLOW_SIZE) {
|
||||
reject(new Error(ImageUploadStatusType.ERROR_EXCEED_SIZE));
|
||||
this._decreaseWaiting();
|
||||
return;
|
||||
}
|
||||
// 获取上传的图片的宽高
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.addEventListener('load', evt => {
|
||||
const replaceSrc = evt.target?.result as string;
|
||||
if (replaceSrc == null) {
|
||||
reject(new Error(ImageUploadStatusType.ERROR_IMAGE));
|
||||
this._decreaseWaiting();
|
||||
return;
|
||||
}
|
||||
|
||||
const imageId = generateRandomId(6);
|
||||
resolve({
|
||||
imageId,
|
||||
imageSourceType: ImageSourceType.BASE64,
|
||||
source: replaceSrc,
|
||||
base64Cache: replaceSrc,
|
||||
status: ImageUploadStatusType.SUCCUSS,
|
||||
});
|
||||
|
||||
this._decreaseWaiting();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setWaitCount(count: number) {
|
||||
this._waitCount = count;
|
||||
this._change$.next(count);
|
||||
}
|
||||
|
||||
private _decreaseWaiting() {
|
||||
this._waitCount -= 1;
|
||||
this._change$.next(this._waitCount);
|
||||
}
|
||||
|
||||
private _getSkeleton(unitId: string, subUnitId: string) {
|
||||
return this._accessor.get(IRenderManagerService)?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getOrCreateSkeleton({ sheetId: subUnitId });
|
||||
}
|
||||
|
||||
private _sheet() {
|
||||
const unit: Nullable<Workbook> = this._univerInstanceService.getCurrentUnitForType<Workbook>(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
);
|
||||
if (!unit) {
|
||||
return null;
|
||||
}
|
||||
const unitId = unit.getUnitId();
|
||||
const sheet: Worksheet = unit.getActiveSheet();
|
||||
const subUnitId = sheet?.getSheetId();
|
||||
return {
|
||||
unit,
|
||||
sheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
};
|
||||
}
|
||||
}
|
||||
+794
@@ -0,0 +1,794 @@
|
||||
import type { Workbook } from '@univerjs/core';
|
||||
import type { ITextWatermarkConfig } from '@univerjs/engine-render';
|
||||
|
||||
import { Disposable, DisposableCollection, generateRandomId, Inject, Injector, IUniverInstanceService } from '@univerjs/core';
|
||||
import {
|
||||
CanvasRenderMode,
|
||||
DEFAULT_FONTFACE_PLANE,
|
||||
Engine,
|
||||
IRenderManagerService,
|
||||
Scene,
|
||||
SHEET_VIEWPORT_KEY,
|
||||
Spreadsheet,
|
||||
SpreadsheetSkeleton,
|
||||
Viewport,
|
||||
} from '@univerjs/engine-render';
|
||||
import { UniverType } from '@univerjs/protocol';
|
||||
import { SheetPrintInterceptorService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
import { translateHeaderFooterText } from '../utils';
|
||||
import { JnpfPrintAlignEnum, JnpfPrintCustomFooterEnum, JnpfPrintCustomHeaderEnum, JnpfPrintOtherParamsEnum } from '../utils/define';
|
||||
import { buildUUID } from '../utils/uuid';
|
||||
|
||||
export class ZqSheetsPrintUiService extends Disposable {
|
||||
// 获取容器元素
|
||||
get container() {
|
||||
return this._containerEle;
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
private _containerEle = document.createElement('div');
|
||||
private _dirty: boolean = false;
|
||||
|
||||
private _effects: DisposableCollection = new DisposableCollection();
|
||||
private _engine!: Engine;
|
||||
private _key: string;
|
||||
private _layoutConfig: any;
|
||||
|
||||
private _leftTopViewport!: Viewport;
|
||||
private _leftViewport!: Viewport;
|
||||
private _mainViewport!: Viewport;
|
||||
|
||||
private readonly _previewContainerScale: number;
|
||||
private readonly _printConfig: any;
|
||||
private _root: HTMLDivElement = document.createElement('div');
|
||||
|
||||
private _scene!: Scene;
|
||||
|
||||
private readonly _sceneKey: string;
|
||||
private _sheetPrintInterceptorService: SheetPrintInterceptorService;
|
||||
private readonly _skeleton!: SpreadsheetSkeleton;
|
||||
private _spreadsheetObject: any;
|
||||
|
||||
private _topViewport!: Viewport;
|
||||
|
||||
private _totalHeight: number = 0;
|
||||
private _totalWidth: number = 0;
|
||||
private _univerInstanceService: IUniverInstanceService;
|
||||
private readonly _watermarkConfig: any;
|
||||
|
||||
constructor(
|
||||
@Inject(Injector) private readonly _accessor: Injector,
|
||||
layoutConfig = {},
|
||||
printConfig = {},
|
||||
watermarkConfig = {},
|
||||
previewContainerScale = 1, // 打印预览容器的伸缩比
|
||||
) {
|
||||
super();
|
||||
|
||||
this._univerInstanceService = this._getUniverInstanceService();
|
||||
this._sheetPrintInterceptorService = this._getSheetPrintInterceptorService();
|
||||
|
||||
this._layoutConfig = layoutConfig as any;
|
||||
this._printConfig = printConfig as any;
|
||||
this._watermarkConfig = watermarkConfig as any;
|
||||
this._previewContainerScale = previewContainerScale;
|
||||
|
||||
const { subUnitId, unitId } = this._layoutConfig;
|
||||
this._sceneKey = this._getSceneKey(unitId, subUnitId);
|
||||
|
||||
this._key = `${unitId}_${subUnitId}`;
|
||||
const skeleton = this._getSkeleton(unitId, subUnitId);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
this._skeleton = skeleton;
|
||||
this._root.className = '__root';
|
||||
this._initRender(); // 初始化渲染
|
||||
}
|
||||
|
||||
public _prepare() {
|
||||
this._resizeViewport();
|
||||
this._effects.dispose();
|
||||
this._effects = new DisposableCollection();
|
||||
const { offsetX, offsetY, scale } = this._getTranslateOffset();
|
||||
const { subUnitId, unitId } = this._layoutConfig;
|
||||
this._root.style.left = `${offsetX}px`;
|
||||
this._root.style.top = `${offsetY}px`;
|
||||
const width = this._totalWidth * scale;
|
||||
const height = this._totalHeight * scale;
|
||||
this._root.style.width = `${width}px`;
|
||||
this._root.style.height = `${height}px`;
|
||||
this._root.style.position = 'absolute';
|
||||
this._root.style.overflow = 'hidden';
|
||||
this._sheetPrintInterceptorService.interceptor.fetchThroughInterceptors(
|
||||
this._sheetPrintInterceptorService.interceptor.getInterceptPoints().PRINTING_DOM_COLLECT,
|
||||
)(this._effects, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
scene: this._scene,
|
||||
engine: this._engine,
|
||||
root: this._root,
|
||||
worksheet: this._skeleton.worksheet,
|
||||
skeleton: this._skeleton,
|
||||
offset: this._getTranslateOffset(),
|
||||
});
|
||||
}
|
||||
|
||||
clearMemory() {
|
||||
this._skeleton.resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
dispose() {
|
||||
super.dispose(); // 调用父类的dispose方法
|
||||
|
||||
if (this._containerEle.parentElement) {
|
||||
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
||||
this._containerEle.parentElement?.removeChild(this._containerEle); // 移除容器
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听变换并在准备好时渲染
|
||||
*/
|
||||
renderOnReady() {
|
||||
const observable = this._engine?.onTransformChange$?.subscribeEvent(() => {
|
||||
this.renderPage();
|
||||
});
|
||||
|
||||
this.disposeWithMe(observable); // 订阅事件并在销毁时清理
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染页面
|
||||
*/
|
||||
renderPage() {
|
||||
this._clearEngineCanvas(); // 清除画布
|
||||
|
||||
this._renderMainScene(); // 渲染主场景(核心内容)
|
||||
this._renderHeaderFooter(); // 渲染页眉页脚
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记当前状态为脏(需要重新渲染)
|
||||
*/
|
||||
setDirty(state: boolean) {
|
||||
this._dirty = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将电子表格对象添加到场景中
|
||||
*/
|
||||
private _addSpreadsheetToScene() {
|
||||
// 创建电子表格对象,传入唯一标识符、骨架对象,并设置初始化选项
|
||||
const spreadsheet = new Spreadsheet('__JnpfSheetPrintRender__', this._skeleton, false);
|
||||
|
||||
// 将电子表格设置为打印模式,确保以打印样式进行渲染
|
||||
spreadsheet.isPrinting = true;
|
||||
|
||||
// 将电子表格对象添加到当前场景中,纳入渲染流程
|
||||
this._scene.addObject(spreadsheet);
|
||||
|
||||
// 缓存电子表格对象,便于后续访问或操作
|
||||
this._spreadsheetObject = spreadsheet;
|
||||
|
||||
// 从布局配置中获取单元 ID 和子单元 ID,用于拦截器逻辑
|
||||
const { subUnitId, unitId } = this._layoutConfig ?? {};
|
||||
|
||||
// 获取拦截器实例与拦截点,确保存在后再进行触发
|
||||
const interceptor = this._sheetPrintInterceptorService?.interceptor;
|
||||
const interceptPoints = interceptor?.getInterceptPoints();
|
||||
const printCollectPoint = interceptPoints?.PRINTING_COMPONENT_COLLECT;
|
||||
|
||||
// 如果拦截器和拦截点存在,则触发拦截逻辑
|
||||
if (interceptor && printCollectPoint) {
|
||||
interceptor.fetchThroughInterceptors(printCollectPoint)(
|
||||
undefined, // 没有特定请求参数,传入 undefined
|
||||
{
|
||||
scene: this._scene,
|
||||
subUnitId,
|
||||
unitId,
|
||||
engine: this._engine,
|
||||
root: this._root,
|
||||
worksheet: this._skeleton.worksheet,
|
||||
skeleton: this._skeleton,
|
||||
offset: this._getTranslateOffset(),
|
||||
spreadsheet: this._spreadsheetObject,
|
||||
}, // 将单元 ID、子单元 ID 和场景信息传递给拦截器
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加并初始化多个视口(Viewport)到场景中
|
||||
*/
|
||||
private _addViewport() {
|
||||
// 从视口键名常量中解构出各个视口的标识符
|
||||
const { VIEW_MAIN, VIEW_MAIN_LEFT, VIEW_MAIN_LEFT_TOP, VIEW_MAIN_TOP } = SHEET_VIEWPORT_KEY;
|
||||
|
||||
// 创建主视口,负责渲染主要内容区域
|
||||
this._mainViewport = new Viewport(VIEW_MAIN, this._scene);
|
||||
|
||||
// 创建左侧视口,负责渲染左侧固定区域
|
||||
this._leftViewport = new Viewport(VIEW_MAIN_LEFT, this._scene);
|
||||
|
||||
// 创建顶部视口,负责渲染顶部固定区域
|
||||
this._topViewport = new Viewport(VIEW_MAIN_TOP, this._scene);
|
||||
|
||||
// 创建左上角视口,负责渲染左上角交叉固定区域
|
||||
this._leftTopViewport = new Viewport(VIEW_MAIN_LEFT_TOP, this._scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空引擎内画布
|
||||
*/
|
||||
private _clearEngineCanvas() {
|
||||
this._engine?.clearCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制水印
|
||||
* @param ctx Canvas 2D 渲染上下文
|
||||
* @param config 水印配置参数
|
||||
*/
|
||||
private _drawWatermark(ctx: CanvasRenderingContext2D, config: ITextWatermarkConfig) {
|
||||
const { bold, color, content, direction, fontSize: fontSizeConf, italic, opacity, repeat, rotate, spacingX, spacingY, x, y } = config;
|
||||
|
||||
if (!content) return;
|
||||
|
||||
const fontSize = fontSizeConf * this._previewContainerScale;
|
||||
const { h, w } = this._getPaperContainerSize();
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = opacity;
|
||||
|
||||
const fontWeight = bold ? 'bold' : 'normal';
|
||||
const fontStyle = italic ? 'italic' : 'normal';
|
||||
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${DEFAULT_FONTFACE_PLANE}`;
|
||||
ctx.fillStyle = color;
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// **计算文本尺寸**
|
||||
const textWidth = ctx.measureText(content).width;
|
||||
const textHeight = fontSize; // 大部分情况下,高度近似等于字体大小
|
||||
|
||||
// **方向控制**
|
||||
let stepX = spacingX + textWidth; // 修正水平间距
|
||||
let startX = x;
|
||||
let textAlign: CanvasTextAlign = 'left';
|
||||
|
||||
if (direction === 'rtl') {
|
||||
stepX = -(spacingX + textWidth);
|
||||
startX = w - x;
|
||||
textAlign = 'right';
|
||||
}
|
||||
|
||||
ctx.textAlign = textAlign;
|
||||
|
||||
if (repeat) {
|
||||
// **修正垂直间距**
|
||||
const stepY = spacingY + textHeight;
|
||||
|
||||
// **计算水印覆盖区域,防止边缘空白**
|
||||
const cols = Math.ceil(w / Math.abs(stepX)) + 2;
|
||||
const rows = Math.ceil(h / stepY) + 2;
|
||||
|
||||
for (let col = -1; col < cols; col++) {
|
||||
for (let row = -1; row < rows; row++) {
|
||||
const posX = startX + col * stepX;
|
||||
const posY = y + row * stepY;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(posX, posY);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.fillText(content, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.save();
|
||||
ctx.translate(startX, y);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.fillText(content, 0, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 省略号裁剪文本
|
||||
*/
|
||||
private _ellipsisText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
||||
if (ctx.measureText(text).width <= maxWidth) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let truncated = text;
|
||||
while (ctx.measureText(`${truncated}...`).width > maxWidth && truncated.length > 0) {
|
||||
truncated = truncated.slice(0, -1);
|
||||
}
|
||||
|
||||
return `${truncated}...`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取纸张容器的尺寸
|
||||
*/
|
||||
private _getPaperContainerSize() {
|
||||
// 从布局配置中解构出纸张尺寸(宽度 w 和高度 h)
|
||||
const { h = 0, w = 0 } = this._layoutConfig?.paperSize ?? {};
|
||||
|
||||
// 检查宽度和高度是否有效
|
||||
if (w <= 0 || h <= 0) {
|
||||
console.warn('纸张尺寸配置无效,宽度或高度小于等于0。');
|
||||
}
|
||||
|
||||
// 根据预览容器的缩放比例,计算实际渲染的尺寸
|
||||
const scaledWidth = w * this._previewContainerScale; // 宽度按缩放比例调整
|
||||
const scaledHeight = h * this._previewContainerScale; // 高度按缩放比例调整
|
||||
|
||||
// 返回计算后的尺寸对象
|
||||
return {
|
||||
h: scaledHeight,
|
||||
w: scaledWidth,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取纸张边距(Padding)
|
||||
*/
|
||||
private _getPaperPadding() {
|
||||
const { bottom = 0, left = 0, right = 0, top = 0 } = this._layoutConfig?.paperPadding ?? {};
|
||||
const previewContainerScale = this._previewContainerScale;
|
||||
|
||||
return {
|
||||
bottom: bottom * previewContainerScale,
|
||||
left: left * previewContainerScale,
|
||||
right: right * previewContainerScale,
|
||||
top: top * previewContainerScale,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得场景专一key
|
||||
* @param unitId
|
||||
* @param subUnitId
|
||||
*/
|
||||
private _getSceneKey(unitId: string, subUnitId: string) {
|
||||
return `${unitId}_${subUnitId}_${buildUUID()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到sheetPrintInterceptorService
|
||||
*/
|
||||
private _getSheetPrintInterceptorService() {
|
||||
return this._accessor.get(SheetPrintInterceptorService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到skeleton
|
||||
* @param unitId
|
||||
* @param subUnitId
|
||||
*/
|
||||
private _getSkeleton(unitId: string, subUnitId: string) {
|
||||
return this._accessor.get(IRenderManagerService)?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getOrCreateSkeleton({ sheetId: subUnitId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平移偏移量,用于将打印内容定位到页面上的正确位置
|
||||
*
|
||||
* 根据纸张的尺寸、内边距、打印配置中的对齐方式以及总内容的宽度和高度,计算水平和垂直方向的偏移量。
|
||||
* 最终返回的偏移量用于定位打印内容的起始位置,以便它能够根据配置进行正确的对齐。
|
||||
*/
|
||||
private _getTranslateOffset() {
|
||||
// 获取纸张的内边距和纸张容器的尺寸
|
||||
const paperContainerSize = this._getPaperContainerSize();
|
||||
const paperPadding = this._getPaperPadding();
|
||||
|
||||
// 解构获取打印配置中的对齐方式(水平对齐和垂直对齐)
|
||||
const { hAlign, vAlign } = this._printConfig;
|
||||
|
||||
// 计算最大缩放比例,确保内容在X和Y方向上都能够缩放适应
|
||||
const scale = Math.max(this._scene.scaleX, this._scene.scaleY);
|
||||
|
||||
// 计算水平偏移量(offsetX)
|
||||
let offsetX: number;
|
||||
if (hAlign === JnpfPrintAlignEnum.start) {
|
||||
// 水平对齐方式为左对齐,偏移量等于左边距
|
||||
offsetX = paperPadding.left;
|
||||
} else if (hAlign === JnpfPrintAlignEnum.end) {
|
||||
// 水平对齐方式为右对齐,偏移量等于纸张宽度减去内容宽度和右边距
|
||||
offsetX = paperContainerSize.w - this._totalWidth * scale - paperPadding.right;
|
||||
} else {
|
||||
// 水平对齐方式为居中对齐,计算居中位置
|
||||
offsetX = paperPadding.left + (paperContainerSize.w - this._totalWidth * scale - paperPadding.left - paperPadding.right) / 2;
|
||||
}
|
||||
|
||||
// 计算垂直偏移量(offsetY)
|
||||
let offsetY: number;
|
||||
if (vAlign === JnpfPrintAlignEnum.start) {
|
||||
// 垂直对齐方式为上对齐,偏移量等于上边距
|
||||
offsetY = paperPadding.top;
|
||||
} else if (vAlign === JnpfPrintAlignEnum.end) {
|
||||
// 垂直对齐方式为下对齐,偏移量等于纸张高度减去内容高度和下边距
|
||||
offsetY = paperContainerSize.h - this._totalHeight * scale - paperPadding.bottom;
|
||||
} else {
|
||||
// 垂直对齐方式为居中对齐,计算居中位置
|
||||
offsetY = (paperContainerSize.h - this._totalHeight * scale - paperPadding.top - paperPadding.bottom) / 2 + paperPadding.top;
|
||||
}
|
||||
|
||||
// 返回四舍五入后的偏移量,确保偏移值为整数
|
||||
return { offsetX: Math.round(offsetX), offsetY: Math.round(offsetY), scale };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到univerInstanceService
|
||||
*/
|
||||
private _getUniverInstanceService() {
|
||||
return this._accessor.get(IUniverInstanceService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化渲染引擎、场景、视口,并配置缩放比例和资源管理。
|
||||
*/
|
||||
private _initRender() {
|
||||
// 获取纸张容器的宽度和高度,确保渲染区域的尺寸准确
|
||||
const { h: paperContainerH, w: paperContainerW } = this._getPaperContainerSize();
|
||||
|
||||
// 设置像素比为 1,通常用于标准屏幕显示
|
||||
const pixelRatio = 1;
|
||||
|
||||
// 创建渲染引擎实例
|
||||
// 参数:宽度、高度、像素比、渲染模式
|
||||
this._engine = new Engine('', {
|
||||
elementWidth: paperContainerW,
|
||||
elementHeight: paperContainerH,
|
||||
dpr: pixelRatio,
|
||||
renderMode: CanvasRenderMode.Printing, // 设置渲染模式为打印模式(Printing),确保画布的内容适配打印需求
|
||||
});
|
||||
|
||||
// 创建场景对象,并将其与渲染引擎关联
|
||||
// 参数:场景的唯一标识符、渲染引擎实例
|
||||
this._scene = new Scene(this._sceneKey, this._engine);
|
||||
|
||||
// 将渲染引擎绑定到指定的 DOM 容器中
|
||||
// 参数:容器元素、是否清除现有内容(false 表示不清除)
|
||||
this._engine.setContainer(this._containerEle, false);
|
||||
this._engine
|
||||
.getCanvas()
|
||||
.getContext()
|
||||
.setId(`${this._key}_${generateRandomId(4)}`);
|
||||
// 从布局配置中获取打印缩放比例,若未配置则默认为 1
|
||||
const { printScale = 1 } = this._layoutConfig;
|
||||
|
||||
// 计算场景的最终缩放比例,结合打印缩放比例和预览容器的缩放比例
|
||||
const sceneScale = printScale * this._previewContainerScale;
|
||||
|
||||
// 将计算得到的缩放比例应用到场景中
|
||||
// 参数:x 方向缩放比例,y 方向缩放比例
|
||||
this._scene.scale(sceneScale, sceneScale);
|
||||
|
||||
// 将电子表格组件添加到场景中
|
||||
this._addSpreadsheetToScene();
|
||||
|
||||
// 创建视口并与当前场景关联,确保视口正确渲染内容
|
||||
this._addViewport();
|
||||
|
||||
// 注册资源销毁回调,确保在对象生命周期结束时释放资源,防止内存泄漏
|
||||
this.disposeWithMe({
|
||||
dispose: () => {
|
||||
// 释放引擎资源,确保渲染引擎的内存被回收
|
||||
this._engine.dispose();
|
||||
|
||||
// 释放场景资源,确保场景相关的内存被回收
|
||||
this._scene.dispose();
|
||||
this.clearMemory();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染页眉和页脚
|
||||
*/
|
||||
private _renderHeaderFooter() {
|
||||
// 从布局配置中解构获取单元ID和子单元ID
|
||||
const { subUnitId, unitId } = this._layoutConfig ?? {};
|
||||
|
||||
// 获取工作簿对象
|
||||
const workbook = this._univerInstanceService?.getUnit<Workbook>(unitId, UniverType.UNIVER_SHEET);
|
||||
if (!workbook) return; // 如果未找到工作簿,直接返回
|
||||
|
||||
// 获取工作表对象
|
||||
const worksheet = workbook?.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) return; // 如果未找到工作表,直接返回
|
||||
|
||||
// 获取Canvas上下文对象
|
||||
const ctx = this._engine?.getCanvas()?.getContext();
|
||||
ctx.save(); // 保存当前绘图状态
|
||||
ctx.font = `13px ${DEFAULT_FONTFACE_PLANE}`; // 设置字体样式
|
||||
|
||||
// 获取纸张尺寸并计算绘制位置
|
||||
const { h, w } = this._getPaperContainerSize();
|
||||
const x = 20 * this._previewContainerScale;
|
||||
const y = 20 * this._previewContainerScale;
|
||||
|
||||
// 获取页眉页脚的参数
|
||||
const { headerFooterParams, headerFooterType } = this._printConfig ?? {};
|
||||
|
||||
if (headerFooterType === 'default') {
|
||||
const maxTextWidth = (w - 2 * x) / 2;
|
||||
|
||||
// 【左上角】渲染工作簿名称
|
||||
if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.workbookTitle)) {
|
||||
const truncatedText = this._ellipsisText(ctx, this._printConfig.workbookTitleText, maxTextWidth);
|
||||
ctx.fillText(truncatedText, x, y);
|
||||
}
|
||||
|
||||
// 【右上角】渲染工作表名称
|
||||
if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.worksheetTitle)) {
|
||||
const sheetName = worksheet?.getName() ?? '';
|
||||
const truncatedText = this._ellipsisText(ctx, sheetName, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, w - textWidth - x, y); // 右对齐
|
||||
}
|
||||
|
||||
// 【左下角】渲染日期和时间
|
||||
if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate) || headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime)) {
|
||||
let dateStr = new Date().toLocaleString();
|
||||
if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate) && !headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime)) {
|
||||
dateStr = new Date().toLocaleDateString();
|
||||
} else if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime) && !headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate)) {
|
||||
dateStr = new Date().toLocaleTimeString();
|
||||
}
|
||||
|
||||
const truncatedText = this._ellipsisText(ctx, dateStr, maxTextWidth);
|
||||
ctx.fillText(truncatedText, x, h - y); // 绘制时间/日期
|
||||
}
|
||||
|
||||
// 【右下角】渲染页码
|
||||
if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.pageNumber)) {
|
||||
const pageNumberText = `${this._layoutConfig?.sheetPageNumber}`;
|
||||
const truncatedText = this._ellipsisText(ctx, pageNumberText, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, w - textWidth - x, h - y); // 右对齐
|
||||
}
|
||||
} else {
|
||||
const maxTextWidth = (w - 3 * x) / 3;
|
||||
|
||||
const printConfigCache = {
|
||||
...this._printConfig,
|
||||
bookPageNumber: this._layoutConfig?.bookPageNumber,
|
||||
bookTotalPage: this._layoutConfig?.bookTotalPage,
|
||||
sheetPageNumber: this._layoutConfig?.sheetPageNumber,
|
||||
sheetTotalPage: this._layoutConfig?.sheetTotalPage,
|
||||
workSheetTitleText: worksheet?.getName(),
|
||||
};
|
||||
|
||||
// 左上角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.leftTop)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.leftTop, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
ctx.fillText(truncatedText, x, y);
|
||||
}
|
||||
|
||||
// 中上角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.centerTop)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.centerTop, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, (w - textWidth) / 2, y);
|
||||
}
|
||||
|
||||
// 右上角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.rightTop)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.rightTop, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, w - textWidth - x, y);
|
||||
}
|
||||
|
||||
// 左下角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.leftBottom)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.leftBottom, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
ctx.fillText(truncatedText, x, h - y);
|
||||
}
|
||||
|
||||
// 中下角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.centerBottom)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.centerBottom, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, (w - textWidth) / 2, h - y);
|
||||
}
|
||||
|
||||
// 右下角渲染
|
||||
if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.rightBottom)) {
|
||||
const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.rightBottom, printConfigCache);
|
||||
const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
|
||||
const textMetrics = ctx.measureText(truncatedText);
|
||||
const textWidth = textMetrics.width;
|
||||
ctx.fillText(truncatedText, w - textWidth - x, h - y);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore(); // 恢复绘图状态
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染主场景
|
||||
*/
|
||||
private _renderMainScene() {
|
||||
// 如果场景被标记为脏,需要重新调整视口大小
|
||||
this._dirty && this._prepare();
|
||||
|
||||
// 获取Canvas的绘图上下文
|
||||
const context = this._engine.getCanvas().getContext();
|
||||
|
||||
// 获取打印配置中的网格线设置和偏移量
|
||||
const { gridlines } = this._printConfig;
|
||||
const { offsetX, offsetY } = this._getTranslateOffset();
|
||||
|
||||
// 根据网格线设置禁用或启用网格线
|
||||
this._spreadsheetObject.setForceDisableGridlines(!gridlines);
|
||||
|
||||
// 强制标记表格为脏,以便重新渲染
|
||||
this._spreadsheetObject.makeForceDirty();
|
||||
this._scene.makeDirty();
|
||||
|
||||
// 保存当前绘图上下文状态
|
||||
context.save();
|
||||
// 应用偏移量
|
||||
context.translateWithPrecision(offsetX, offsetY);
|
||||
|
||||
// 渲染场景
|
||||
this._scene.render();
|
||||
|
||||
// 恢复绘图上下文状态
|
||||
context.restore();
|
||||
|
||||
// 添加水印(判断下,如果不需要水印就不用配置了)!!!目前和0.6.2版本一致,都是盖在了内容上面
|
||||
if (this._watermarkConfig?.show) {
|
||||
this._drawWatermark(context, this._watermarkConfig?.config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整视口大小和滚动值
|
||||
*/
|
||||
private _resizeViewport() {
|
||||
// 从布局配置中解构冻结和范围设置
|
||||
const { freeze, printScale = 1, range } = this._layoutConfig;
|
||||
// 从冻结设置中解构x轴和y轴的分割点以及起始行列
|
||||
const { startColumn, startRow, xSplit, ySplit } = freeze;
|
||||
// 从骨架配置中解构行头宽度和列头高度
|
||||
const { columnHeaderHeight, rowHeaderWidth } = this._skeleton;
|
||||
|
||||
// 缩放场景以适应预览容器的缩放比例
|
||||
const sceneScale = printScale * this._previewContainerScale;
|
||||
this._scene.scale(sceneScale, sceneScale);
|
||||
|
||||
// 获取无合并单元格的位置,基于起始行列
|
||||
const cellPosition = this._skeleton?.getNoMergeCellPositionByIndexWithNoHeader(startRow, startColumn);
|
||||
// 获取无合并单元格的位置,基于起始行列减去分割点
|
||||
const cellPositionSplit = this._skeleton?.getNoMergeCellPositionByIndexWithNoHeader(startRow - ySplit, startColumn - xSplit);
|
||||
|
||||
// 计算起始X分割位置
|
||||
const startXSplit = xSplit > 0 ? cellPosition.startX - cellPositionSplit.startX : 0;
|
||||
// 计算起始Y分割位置
|
||||
const startYSplit = ySplit > 0 ? cellPosition.startY - cellPositionSplit.startY : 0;
|
||||
|
||||
// 获取场景的精度缩放比例
|
||||
const precisionScale = this._scene.getPrecisionScale();
|
||||
// 定义用于缩放X值的函数
|
||||
const getScaleX = (value: number) => Math.round(value * precisionScale.scaleX) / precisionScale.scaleX;
|
||||
// 定义用于缩放Y值的函数
|
||||
const getScaleY = (value: number) => Math.round(value * precisionScale.scaleY) / precisionScale.scaleY;
|
||||
|
||||
// 获取范围起始单元格的位置
|
||||
const startCellPosition = this._skeleton.getNoMergeCellPositionByIndexWithNoHeader(range.startRow, range.startColumn);
|
||||
// 计算结束单元格的位置,并处理边界情况
|
||||
const endCellPosition = this._skeleton.getNoMergeCellPositionByIndexWithNoHeader(range.endRow, range.endColumn);
|
||||
|
||||
// 定义范围对象,包含起始和结束的X、Y坐标
|
||||
const _range = {
|
||||
endX: xSplit > 0 ? cellPosition.startX : 0,
|
||||
endY: ySplit > 0 ? cellPosition.startY : 0,
|
||||
startX: xSplit > 0 ? cellPositionSplit.startX : 0,
|
||||
startY: ySplit > 0 ? cellPositionSplit.startY : 0,
|
||||
};
|
||||
|
||||
const endX = Math.max(0, getScaleX(endCellPosition.endX) - Math.max(startCellPosition.startX, _range.endX));
|
||||
const endY = Math.max(0, getScaleY(endCellPosition.endY) - getScaleY(Math.max(startCellPosition.startY, _range.endY)));
|
||||
// 计算缩放比例
|
||||
const scale = 1 / Math.max(precisionScale.scaleX, precisionScale.scaleY);
|
||||
|
||||
this._totalWidth = startXSplit + endX + scale;
|
||||
this._totalHeight = startYSplit + endY + scale;
|
||||
|
||||
const startSplit = { x: startXSplit, y: startYSplit };
|
||||
|
||||
const scaleXY = {
|
||||
x: getScaleX(endCellPosition.endX) - getScaleX(endX) - getScaleX(Math.max(startCellPosition.startX, _range.endX)),
|
||||
y: getScaleY(endCellPosition.endY) - getScaleY(endY) - getScaleY(Math.max(startCellPosition.startY, _range.endY)),
|
||||
};
|
||||
|
||||
// 如果x轴和y轴都有分割点,启用左上角视口并调整其大小和滚动值
|
||||
if (xSplit > 0 && ySplit > 0) {
|
||||
this._leftTopViewport.enable();
|
||||
this._leftTopViewport.resizeWhenFreezeChange({
|
||||
height: getScaleY(startSplit.y),
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: getScaleX(startSplit.x),
|
||||
});
|
||||
this._leftTopViewport.updateScrollVal({
|
||||
viewportScrollX: getScaleX(_range.startX) + getScaleX(rowHeaderWidth),
|
||||
viewportScrollY: getScaleY(_range.startY) + getScaleY(columnHeaderHeight),
|
||||
});
|
||||
} else {
|
||||
// 否则禁用左上角视口
|
||||
this._leftTopViewport.disable();
|
||||
}
|
||||
|
||||
// 如果x轴有分割点,启用左视口并调整其大小和滚动值
|
||||
if (xSplit > 0) {
|
||||
this._leftViewport.enable();
|
||||
this._leftViewport.resizeWhenFreezeChange({
|
||||
height: getScaleY(endY) + scale + scaleXY.y,
|
||||
left: 0,
|
||||
top: getScaleY(startSplit.y),
|
||||
width: getScaleX(startSplit.x),
|
||||
});
|
||||
this._leftViewport.updateScrollVal({
|
||||
viewportScrollX: getScaleX(_range.startX) + getScaleX(rowHeaderWidth),
|
||||
viewportScrollY: getScaleY(startCellPosition.startY) + getScaleY(columnHeaderHeight) - getScaleY(startSplit.y),
|
||||
});
|
||||
} else {
|
||||
// 否则禁用左视口
|
||||
this._leftViewport.disable();
|
||||
}
|
||||
|
||||
// 如果y轴有分割点,启用顶视口并调整其大小和滚动值
|
||||
if (ySplit > 0) {
|
||||
this._topViewport.enable();
|
||||
this._topViewport.resizeWhenFreezeChange({
|
||||
height: getScaleY(startSplit.y),
|
||||
left: getScaleX(startSplit.x),
|
||||
top: 0,
|
||||
width: getScaleX(endX) + scale + scaleXY.x,
|
||||
});
|
||||
this._topViewport.updateScrollVal({
|
||||
viewportScrollX: getScaleX(startCellPosition.startX) + getScaleX(rowHeaderWidth) - getScaleX(startSplit.x),
|
||||
viewportScrollY: getScaleY(_range.startY) + getScaleY(columnHeaderHeight),
|
||||
});
|
||||
} else {
|
||||
// 否则禁用顶视口
|
||||
this._topViewport.disable();
|
||||
}
|
||||
|
||||
// 调整主视口的大小和滚动值
|
||||
this._mainViewport.resizeWhenFreezeChange({
|
||||
height: getScaleY(endY) + scale + scaleXY.y,
|
||||
left: getScaleX(startSplit.x),
|
||||
top: getScaleY(startSplit.y),
|
||||
width: getScaleX(endX) + scale + scaleXY.x,
|
||||
});
|
||||
this._mainViewport.updateScrollVal({
|
||||
viewportScrollX: getScaleX(startCellPosition.startX) + getScaleX(rowHeaderWidth) - getScaleX(startSplit.x),
|
||||
viewportScrollY: getScaleY(startCellPosition.startY) + getScaleY(columnHeaderHeight) - getScaleY(startSplit.y),
|
||||
});
|
||||
}
|
||||
}
|
||||
+792
@@ -0,0 +1,792 @@
|
||||
import type { IFreeze, Workbook } from '@univerjs/core';
|
||||
import type { SpreadsheetSkeleton } from '@univerjs/engine-render';
|
||||
|
||||
import { Disposable, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetPrintInterceptorService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
import { isEmptyObject, isNullOrUndefined } from '../utils';
|
||||
import {
|
||||
DefaultPrintConfig,
|
||||
JnpfPaperPaddingForType,
|
||||
JnpfPrintAreaEnum,
|
||||
JnpfPrintCustomFooterEnum,
|
||||
JnpfPrintCustomHeaderEnum,
|
||||
JnpfPrintDirectionEnum,
|
||||
JnpfPrintFreezeEnum,
|
||||
JnpfPrintOtherParamsEnum,
|
||||
JnpfPrintPaperSizeForType,
|
||||
JnpfPrintScaleEnum,
|
||||
} from '../utils/define';
|
||||
import { buildUUID } from '../utils/uuid';
|
||||
|
||||
export class ZqSheetsPrintService extends Disposable {
|
||||
private _configParams: Record<string, any> = {};
|
||||
private _layoutHeaderFooterParams: any[] = [];
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@Inject(SheetPrintInterceptorService) private readonly _sheetPrintInterceptorService: SheetPrintInterceptorService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置获取打印布局。
|
||||
* @param {any} printConfig - 打印配置对象,默认为 `DefaultPrintConfig`。
|
||||
*/
|
||||
getLayouts(printConfig: any = DefaultPrintConfig) {
|
||||
// 分析布局的页眉和页脚配置,并将结果存储在 `_layoutHeaderFooterParams` 中
|
||||
this._layoutHeaderFooterParams = this._analyseLayoutHeaderFooterConfig(printConfig);
|
||||
|
||||
// 获取参数出来判断是不是存在冻结
|
||||
const subUnitId = this._getWorkbook()!.getActiveSheet()?.getSheetId();
|
||||
if (!subUnitId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = this._getWorkbook()!.save();
|
||||
|
||||
let freeze = [];
|
||||
|
||||
switch (printConfig.printArea) {
|
||||
case JnpfPrintAreaEnum.currentSheet: {
|
||||
const { xSplit = 0, ySplit = 0 } = snapshot?.sheets?.[subUnitId]?.freeze ?? {};
|
||||
|
||||
// 正确的冻结状态判断
|
||||
const colHasFreeze = xSplit > 0; // 列冻结(xSplit 对应列冻结)
|
||||
const rowHasFreeze = ySplit > 0; // 行冻结(ySplit 对应行冻结)
|
||||
|
||||
// 判断冻结状态【判断是单体还是全部,全部的话判断有开就有,没开就没有】
|
||||
freeze = [
|
||||
rowHasFreeze && printConfig?.yFreeze && JnpfPrintFreezeEnum.row, // 行冻结
|
||||
colHasFreeze && printConfig?.xFreeze && JnpfPrintFreezeEnum.column, // 列冻结
|
||||
].filter(Boolean); // 过滤掉 false/null/undefined
|
||||
|
||||
break;
|
||||
}
|
||||
case JnpfPrintAreaEnum.partSheets: {
|
||||
const { sheets } = snapshot;
|
||||
const { partSheets = [] } = printConfig;
|
||||
|
||||
const colHasFreeze =
|
||||
Object.values(sheets).some((sheet: any) => {
|
||||
return partSheets.includes(sheet.id) ? sheet?.freeze?.xSplit > 0 : false;
|
||||
}) && printConfig?.xFreeze;
|
||||
const rowHasFreeze =
|
||||
Object.values(sheets).some((sheet: any) => {
|
||||
return partSheets.includes(sheet.id) ? sheet?.freeze?.ySplit > 0 : false;
|
||||
}) && printConfig?.yFreeze;
|
||||
|
||||
if (colHasFreeze) {
|
||||
freeze.push('column');
|
||||
}
|
||||
if (rowHasFreeze) {
|
||||
freeze.push('row');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case JnpfPrintAreaEnum.workbook: {
|
||||
const { sheets } = snapshot;
|
||||
const colHasFreeze = Object.values(sheets).some((sheet: any) => sheet?.freeze?.xSplit > 0) && printConfig?.xFreeze;
|
||||
const rowHasFreeze = Object.values(sheets).some((sheet: any) => sheet?.freeze?.ySplit > 0) && printConfig?.yFreeze;
|
||||
|
||||
if (colHasFreeze) {
|
||||
freeze.push('column');
|
||||
}
|
||||
if (rowHasFreeze) {
|
||||
freeze.push('row');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
|
||||
// 提取剩余配置
|
||||
const { xFreeze, yFreeze, ...restPrintConfig } = printConfig ?? {};
|
||||
|
||||
// 将配置参数存储在 `_configParams` 中
|
||||
this._configParams = {
|
||||
...restPrintConfig,
|
||||
freeze,
|
||||
};
|
||||
|
||||
// 获取所有需要打印的范围
|
||||
const printRanges = this._computePrintRanges();
|
||||
// 初始化空数组,用于存储计算出的布局
|
||||
const layouts: any[] = [];
|
||||
|
||||
// 遍历每个打印范围,计算对应的页面布局
|
||||
printRanges?.forEach(range => {
|
||||
// 如果当前范围存在,继续处理
|
||||
if (range) {
|
||||
// 计算页面布局
|
||||
const pageLayout = this._computePageLayout(range);
|
||||
|
||||
// 如果页面布局存在,将其添加到 layouts 数组中
|
||||
if (pageLayout) {
|
||||
layouts.push(pageLayout);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 获取纸张大小
|
||||
const paperSize = this._getPaperSize();
|
||||
// 获取纸张边距
|
||||
const paperPadding = this._getPaperPadding();
|
||||
|
||||
// 整个需要打印的页码数量
|
||||
const bookTotalPage = layouts.reduce((total, layout) => total + layout.pages.length, 0);
|
||||
|
||||
let bookPageNumber = 1;
|
||||
const pagesLayout = layouts.flatMap(({ pages: pageRanges, ...info }) =>
|
||||
pageRanges.map((range: any, rangeIndex: any) => ({
|
||||
...info,
|
||||
bookPageNumber: bookPageNumber++,
|
||||
bookTotalPage,
|
||||
browserPreviewScale: 1,
|
||||
layoutId: `layout_${buildUUID()}`,
|
||||
paperPadding,
|
||||
paperSize,
|
||||
range,
|
||||
sheetPageNumber: rangeIndex + 1, // layout 内部页码从 1 开始
|
||||
sheetTotalPage: pageRanges?.length,
|
||||
})),
|
||||
);
|
||||
|
||||
return {
|
||||
pagesLayout,
|
||||
paperSize,
|
||||
printConfig: {
|
||||
...this._configParams,
|
||||
currentDateTime: new Date(Date.now() + 8 * 3600 * 1000).toJSON().slice(0, 19).replace('T', ' '),
|
||||
headerFooterParams: this._layoutHeaderFooterParams,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析页面布局中的页眉和页脚配置。
|
||||
* @param {any} printConfig - 页眉页脚的配置对象,包含多个布尔值属性来决定是否显示某些信息。
|
||||
* @returns 返回需要在页眉或页脚中包含的参数数组。
|
||||
*/
|
||||
private _analyseLayoutHeaderFooterConfig(printConfig: any) {
|
||||
// 定义一个数组,用于存储需要包含的页眉和页脚参数
|
||||
const layoutHeaderFooterParams = [];
|
||||
|
||||
if (printConfig.headerFooterType === 'default') {
|
||||
if (printConfig?.workbookTitle) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.workbookTitle);
|
||||
}
|
||||
if (printConfig?.worksheetTitle) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.worksheetTitle);
|
||||
}
|
||||
if (printConfig?.printDate) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.printDate);
|
||||
}
|
||||
if (printConfig?.printTime) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.printTime);
|
||||
}
|
||||
if (printConfig?.pageNumber) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.pageNumber);
|
||||
}
|
||||
} else {
|
||||
if (printConfig.customHeaderFooterValue?.leftTop) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.leftTop);
|
||||
}
|
||||
if (printConfig.customHeaderFooterValue?.centerTop) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.centerTop);
|
||||
}
|
||||
if (printConfig.customHeaderFooterValue?.rightTop) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.rightTop);
|
||||
}
|
||||
if (printConfig.customHeaderFooterValue?.leftBottom) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.leftBottom);
|
||||
}
|
||||
if (printConfig.customHeaderFooterValue?.centerBottom) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.centerBottom);
|
||||
}
|
||||
if (printConfig.customHeaderFooterValue?.rightBottom) {
|
||||
layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.rightBottom);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回最终解析出来的页眉页脚参数数组
|
||||
return layoutHeaderFooterParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算页面布局,根据给定的打印范围和工作表的骨架信息,计算每一页的布局。
|
||||
* @param printRange {any} 打印范围,包含工作簿信息、工作表信息、打印范围、冻结信息、缩放比例及内容尺寸
|
||||
* @returns 返回计算出的页面布局信息
|
||||
*/
|
||||
private _computePageLayout(printRange: any) {
|
||||
// 解构打印范围信息
|
||||
const { contentSize, freeze, printScale, range, subUnitId, unitId } = printRange ?? {};
|
||||
|
||||
// 获取工作表的骨架信息
|
||||
const skeleton = this._getUnitSkeleton(unitId, subUnitId);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解构工作表的行高和列宽累计值
|
||||
const { columnWidthAccumulation, rowHeightAccumulation } = skeleton;
|
||||
|
||||
// 解构打印范围的行列信息
|
||||
const { endColumn, endRow, startColumn, startRow } = range;
|
||||
|
||||
const calcRow = (start: any) => {
|
||||
let end = start;
|
||||
const offset = start === 0 ? 0 : rowHeightAccumulation[start - 1];
|
||||
for (; end < endRow; end++) {
|
||||
const current = (rowHeightAccumulation[end] - offset) * printScale;
|
||||
const next = (rowHeightAccumulation[end + 1] - offset) * printScale;
|
||||
if (current > contentSize.h || (current < contentSize.h && next > contentSize.h)) return { endRow: end - 1, startRow: start };
|
||||
}
|
||||
return { endRow: end, startRow: start };
|
||||
};
|
||||
|
||||
const calcColumn = (start: any) => {
|
||||
let end = start;
|
||||
const offset = start === 0 ? 0 : columnWidthAccumulation[start - 1];
|
||||
for (; end < endColumn; end++) {
|
||||
const current = (columnWidthAccumulation[end] - offset) * printScale;
|
||||
const next = (columnWidthAccumulation[end + 1] - offset) * printScale;
|
||||
|
||||
if (current >= contentSize.w || (current < contentSize.w && next > contentSize.w)) {
|
||||
return { endColumn: end, startColumn: start };
|
||||
}
|
||||
}
|
||||
return { endColumn: end, startColumn: start };
|
||||
};
|
||||
|
||||
// 计算所有行的分页范围
|
||||
const rows = [];
|
||||
for (let start = startRow; start <= endRow; ) {
|
||||
const row = calcRow(start);
|
||||
rows.push(row);
|
||||
start = row.endRow + 1; // 移动到下一个分页起始行
|
||||
}
|
||||
|
||||
// 计算所有列的分页范围
|
||||
const columns: any[] = [];
|
||||
for (let start = startColumn; start <= endColumn; ) {
|
||||
const column = calcColumn(start);
|
||||
columns.push(column);
|
||||
start = column.endColumn + 1; // 移动到下一个分页起始列
|
||||
}
|
||||
|
||||
// 将行列的分页信息组合成每一页的布局
|
||||
const pages: any[] = [];
|
||||
rows.forEach(row => {
|
||||
columns.forEach(column => {
|
||||
pages.push({ ...row, ...column });
|
||||
});
|
||||
});
|
||||
|
||||
// 返回计算的页面布局信息,包括单位ID、子单位ID、页面信息、冻结信息和缩放比例
|
||||
return { freeze, pages, printScale, subUnitId, unitId };
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并返回打印页面的范围,包括内容区域、冻结行列以及打印比例。
|
||||
* @param {string} subUnitId - 工作表的唯一标识符。
|
||||
* @param {any} sheetRange - 需要打印的工作表范围,包含起始和结束的行列。
|
||||
* @returns 返回打印页面的范围、冻结信息、内容区域和打印比例。
|
||||
*/
|
||||
private _computePrintPageRange(subUnitId: string, sheetRange: any) {
|
||||
// 获取工作簿实例
|
||||
const workbook = this._getWorkbook();
|
||||
|
||||
// 如果没有获取到工作簿,返回 undefined
|
||||
if (!workbook) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作簿的唯一标识符
|
||||
const unitId = workbook!.getUnitId();
|
||||
|
||||
// 获取纸张大小
|
||||
const paperSize = this._getPaperSize();
|
||||
if (!paperSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取纸张的边距
|
||||
const paperPadding = this._getPaperPadding();
|
||||
if (!paperPadding) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算内容区域的大小,减去左右、上下的边距
|
||||
const contentSize = {
|
||||
h: paperSize.h - (paperPadding.top + paperPadding.bottom),
|
||||
w: paperSize.w - (paperPadding.left + paperPadding.right),
|
||||
};
|
||||
|
||||
// 如果起始行列小于等于 -1,返回默认的打印范围,表示无效的打印范围
|
||||
if (sheetRange?.startRow <= -1 || sheetRange?.startColumn <= -1) {
|
||||
return {
|
||||
contentSize,
|
||||
freeze: { startColumn: -1, startRow: -1, xSplit: 0, ySplit: 0 },
|
||||
printScale: 1,
|
||||
range: { endColumn: 0, endRow: 0, startColumn: 0, startRow: 0 },
|
||||
subUnitId,
|
||||
unitId,
|
||||
};
|
||||
}
|
||||
|
||||
// 获取工作表实例
|
||||
const worksheet = workbook!.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作表的冻结信息
|
||||
const freeze = worksheet?.getFreeze();
|
||||
if (!freeze) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作表的骨架信息(如列宽、行高等)
|
||||
const skeleton = this._getUnitSkeleton(unitId, subUnitId);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算打印比例
|
||||
const printScale = this._computePrintScale(skeleton, sheetRange, freeze);
|
||||
|
||||
// 初始化打印范围和冻结信息的默认值
|
||||
let startColumn = -1;
|
||||
let startRow = -1;
|
||||
let xSplit = 0;
|
||||
let ySplit = 0;
|
||||
|
||||
// 获取骨架中的列宽和行高累积数据
|
||||
const { columnWidthAccumulation, rowHeightAccumulation } = skeleton ?? {};
|
||||
let { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow } = sheetRange ?? {};
|
||||
const { startColumn: freezeStartColumn, startRow: freezeStartRow, xSplit: freezeXSplit, ySplit: freezeYSplit } = freeze ?? {};
|
||||
|
||||
// 处理冻结行(如果配置了冻结行)
|
||||
if (freezeYSplit && this._configParams?.freeze.includes(JnpfPrintFreezeEnum?.row)) {
|
||||
// 如果打印范围的结束行大于等于冻结行,则计算冻结区域的高度并调整打印区域的高度
|
||||
if (sheetRangeEndRow >= freezeStartRow) {
|
||||
const offsetHeight = rowHeightAccumulation[freezeStartRow] - rowHeightAccumulation[freezeStartRow - freezeYSplit];
|
||||
|
||||
if (offsetHeight < contentSize?.h) {
|
||||
startRow = freezeStartRow;
|
||||
ySplit = freezeYSplit;
|
||||
contentSize.h -= offsetHeight;
|
||||
}
|
||||
} else {
|
||||
// 如果打印范围的结束行小于冻结行,则计算冻结区域的高度并调整打印区域的高度
|
||||
startRow = sheetRangeEndRow + 1;
|
||||
ySplit = sheetRangeEndRow + 1 - (freezeStartRow - freezeYSplit);
|
||||
contentSize.h = contentSize?.h - rowHeightAccumulation[freezeStartRow] - rowHeightAccumulation[freezeStartRow - freezeYSplit];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新起始行,确保不小于冻结区域的起始行
|
||||
sheetRangeStartRow = Math.max(startRow, sheetRangeStartRow);
|
||||
|
||||
// 处理冻结列(如果配置了冻结列)
|
||||
if (freezeXSplit && this._configParams?.freeze.includes(JnpfPrintFreezeEnum?.column)) {
|
||||
// 如果打印范围的结束列大于等于冻结列,则计算冻结区域的宽度并调整打印区域的宽度
|
||||
if (sheetRangeEndColumn >= freezeStartColumn) {
|
||||
const offsetWidth =
|
||||
printScale * (columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit - 1] || 0));
|
||||
|
||||
if (offsetWidth < contentSize?.w) {
|
||||
startColumn = freezeStartColumn;
|
||||
xSplit = freezeXSplit;
|
||||
contentSize.w -= offsetWidth;
|
||||
}
|
||||
} else {
|
||||
// 如果打印范围的结束列小于冻结列,则计算冻结区域的宽度并调整打印区域的宽度
|
||||
const offsetWidth =
|
||||
printScale * (columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit] - 1 || 0));
|
||||
|
||||
if (offsetWidth < contentSize?.w) {
|
||||
const startColumnCache = sheetRangeEndColumn + 1;
|
||||
const xSplitCache = sheetRangeEndColumn + 1 - (freezeStartColumn - freezeXSplit);
|
||||
|
||||
startColumn = startColumnCache;
|
||||
xSplit = xSplitCache;
|
||||
contentSize.w -= offsetWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新起始列,确保不小于冻结区域的起始列
|
||||
sheetRangeStartColumn = Math.max(startColumn, sheetRangeStartColumn);
|
||||
|
||||
// 返回计算出的打印页面范围,包含打印区域、冻结信息、内容大小和打印比例
|
||||
return {
|
||||
contentSize,
|
||||
freeze: { startColumn, startRow, xSplit, ySplit },
|
||||
printScale,
|
||||
range: { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow },
|
||||
subUnitId,
|
||||
unitId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算打印范围,根据配置的区域选择合适的打印范围。
|
||||
* @returns 返回包含一个或多个打印范围的数组。
|
||||
*/
|
||||
private _computePrintRanges() {
|
||||
// 初始化空数组 result,用于存储计算的打印范围
|
||||
let result: any[] = [];
|
||||
|
||||
// 根据配置的打印区域 执行不同的处理
|
||||
switch (this._configParams.printArea) {
|
||||
// 如果区域是当前选择的区域
|
||||
case JnpfPrintAreaEnum.currentSelection: {
|
||||
// 计算当前选择区域的打印范围,并返回该范围的数组
|
||||
result = [this._computePrintSelectionRange()];
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果区域是当前工作表
|
||||
case JnpfPrintAreaEnum.currentSheet: {
|
||||
// 获取当前活动工作表的 ID
|
||||
const subUnitId = this._getWorkbook()!.getActiveSheet()?.getSheetId();
|
||||
|
||||
// 如果工作表 ID 不存在,则跳出
|
||||
if (!subUnitId) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算当前工作表的打印范围
|
||||
const range = this._computePrintSheetRange(subUnitId);
|
||||
// 如果计算结果有效,返回一个包含该范围的数组
|
||||
result = range ? [range] : [];
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果区域是部分工作簿
|
||||
case JnpfPrintAreaEnum.partSheets: {
|
||||
const partSheets = new Set(this._configParams.partSheets ?? []);
|
||||
// 获取工作簿中的所有工作表
|
||||
const sheets = this._getWorkbook()!.getSheets();
|
||||
|
||||
const targetSheets = sheets.filter(sheet => partSheets.has(sheet?.getSheetId()));
|
||||
|
||||
// 对每个工作表计算打印范围,并过滤掉无效的范围
|
||||
result = targetSheets
|
||||
?.map(sheet => {
|
||||
const subUnitId = sheet?.getSheetId();
|
||||
// 对每个工作表计算并返回其打印范围
|
||||
return this._computePrintSheetRange(subUnitId);
|
||||
})
|
||||
.filter(Boolean); // 过滤掉无效值(如 null 或 undefined)
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果区域是整个工作簿
|
||||
case JnpfPrintAreaEnum.workbook: {
|
||||
// 获取工作簿中的所有工作表
|
||||
const sheets = this._getWorkbook()!.getSheets();
|
||||
|
||||
// 对每个工作表计算打印范围,并过滤掉无效的范围
|
||||
result = sheets
|
||||
?.map(sheet => {
|
||||
const subUnitId = sheet?.getSheetId();
|
||||
// 对每个工作表计算并返回其打印范围
|
||||
return this._computePrintSheetRange(subUnitId);
|
||||
})
|
||||
.filter(Boolean); // 过滤掉无效值(如 null 或 undefined)
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果区域类型不匹配,返回空数组
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回计算出的打印范围数组
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算打印比例。
|
||||
* @param {SpreadsheetSkeleton} skeleton - 工作表的骨架信息,包括行高、列宽累积数据。
|
||||
* @param {any} sheetRange - 打印的单元格范围,包括起始行、起始列、结束行、结束列。
|
||||
* @param {IFreeze} freeze - 冻结范围信息,包括冻结的起始行列和冻结的分割范围。
|
||||
* @returns 返回适合的打印缩放比例,范围在 [0, 1] 之间。
|
||||
*/
|
||||
private _computePrintScale(skeleton: SpreadsheetSkeleton, sheetRange: any, freeze: IFreeze) {
|
||||
// 解构骨架中的列宽和行高累积信息
|
||||
const { columnWidthAccumulation, rowHeightAccumulation } = skeleton ?? {};
|
||||
|
||||
// 骨架信息为空时,返回默认比例 1
|
||||
if (isNullOrUndefined(columnWidthAccumulation) || isNullOrUndefined(rowHeightAccumulation)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 解构打印范围的起始和结束行列
|
||||
const { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow } = sheetRange ?? {};
|
||||
|
||||
// 打印范围信息为空时,返回默认比例 1
|
||||
if (
|
||||
isNullOrUndefined(sheetRangeStartRow) ||
|
||||
isNullOrUndefined(sheetRangeStartColumn) ||
|
||||
isNullOrUndefined(sheetRangeEndRow) ||
|
||||
isNullOrUndefined(sheetRangeEndColumn)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 解构冻结范围的起始行列和分割范围
|
||||
const { startColumn: freezeStartColumn, startRow: freezeStartRow, xSplit: freezeXSplit, ySplit: freezeYSplit } = freeze ?? {};
|
||||
|
||||
// 冻结范围信息为空时,返回默认比例 1
|
||||
if (isNullOrUndefined(freezeXSplit) || isNullOrUndefined(freezeYSplit) || isNullOrUndefined(freezeStartRow) || isNullOrUndefined(freezeStartColumn)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 确定打印范围的起始行:对冻结范围与打印范围取交集
|
||||
const startRow = sheetRangeEndRow < freezeStartRow ? sheetRangeStartRow : Math.max(freezeStartRow, sheetRangeStartRow);
|
||||
|
||||
// 确定打印范围的起始列:对冻结范围与打印范围取交集
|
||||
const startColumn = sheetRangeEndColumn < freezeStartColumn ? sheetRangeStartColumn : Math.max(freezeStartColumn, sheetRangeStartColumn);
|
||||
|
||||
// 获取纸张尺寸
|
||||
const paperSize = this._getPaperSize();
|
||||
if (!paperSize) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 获取纸张边距
|
||||
const paperPadding = this._getPaperPadding();
|
||||
if (!paperPadding) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 计算冻结区域的宽度(列)和高度(行)
|
||||
const xSplit = freezeXSplit > 0 ? columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit - 1] || 0) : 0;
|
||||
const ySplit = freezeYSplit > 0 ? rowHeightAccumulation[freezeStartRow - 1] - (rowHeightAccumulation[freezeStartRow - freezeYSplit - 1] || 0) : 0;
|
||||
|
||||
// 计算打印范围的高度和宽度
|
||||
const rowHeight = rowHeightAccumulation[sheetRangeEndRow] - (rowHeightAccumulation[startRow - 1] || 0);
|
||||
const columnWidth = columnWidthAccumulation[sheetRangeEndColumn] - (columnWidthAccumulation[startColumn - 1] || 0);
|
||||
|
||||
// 加上冻结区域的尺寸,得到总的打印高度和宽度
|
||||
const xScale = columnWidth + xSplit;
|
||||
const yScale = rowHeight + ySplit;
|
||||
|
||||
let result = 1;
|
||||
|
||||
// 根据配置参数计算打印比例
|
||||
switch (this._configParams.printScale) {
|
||||
case JnpfPrintScaleEnum.fitHeight: {
|
||||
// 高度适应比例
|
||||
result = Math.min(1, (paperSize.h - paperPadding.top - paperPadding.bottom) / yScale);
|
||||
break;
|
||||
}
|
||||
case JnpfPrintScaleEnum.fitPage: {
|
||||
// 页面完全适应比例(宽度和高度都适应)
|
||||
result = Math.min(1, (paperSize.w - paperPadding.left - paperPadding.right) / xScale, (paperSize.h - paperPadding.top - paperPadding.bottom) / yScale);
|
||||
break;
|
||||
}
|
||||
case JnpfPrintScaleEnum.fitWidth: {
|
||||
// 宽度适应比例
|
||||
result = Math.min(1, (paperSize.w - paperPadding.left - paperPadding.right) / xScale);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回比例值,保留两位小数
|
||||
return Math.floor(result * 100) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并返回打印选择范围的详细信息。
|
||||
* @returns 返回选区的打印范围信息。
|
||||
*/
|
||||
private _computePrintSelectionRange() {
|
||||
// 获取当前工作簿实例
|
||||
const workbook = this._getWorkbook();
|
||||
|
||||
// 如果没有获取到工作簿,返回 undefined
|
||||
if (!workbook) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作簿的唯一标识符
|
||||
const unitId = workbook!.getUnitId();
|
||||
|
||||
// 获取当前活动工作表
|
||||
const worksheet = workbook!.getActiveSheet();
|
||||
|
||||
// 如果没有获取到工作表,返回 undefined
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前工作表的唯一标识符(subUnitId)
|
||||
const subUnitId = worksheet?.getSheetId();
|
||||
|
||||
// 获取当前工作簿的最后选择区域
|
||||
const lastSelection = this._selectionManagerService?.getWorkbookSelections(unitId)?.getCurrentLastSelection();
|
||||
|
||||
// 如果没有获取到选择区域,返回 undefined
|
||||
if (!lastSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据最后选择的范围计算打印的页面范围
|
||||
return this._computePrintPageRange(subUnitId, lastSelection?.range);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算工作表的打印范围。
|
||||
* @param {string} subUnitId - 工作表的唯一标识符。
|
||||
* @returns 返回计算出的打印范围,包含页面范围、冻结信息等。
|
||||
*/
|
||||
private _computePrintSheetRange(subUnitId: string) {
|
||||
// 获取工作簿实例
|
||||
const workbook = this._getWorkbook();
|
||||
|
||||
// 如果没有获取到工作簿,返回 undefined
|
||||
if (!workbook) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作簿的唯一标识符
|
||||
const unitId = workbook!.getUnitId();
|
||||
|
||||
// 获取当前工作表
|
||||
const worksheet = workbook!.getSheetBySheetId(subUnitId);
|
||||
|
||||
// 如果工作表不存在,返回 undefined
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作表的骨架信息,包含如行列宽度、冻结状态等数据
|
||||
const skeleton = this._getUnitSkeleton(unitId, subUnitId);
|
||||
|
||||
// 如果工作表的骨架信息不存在,返回 undefined
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取工作表的冻结信息,包括冻结行列位置
|
||||
const freeze = worksheet?.getFreeze();
|
||||
|
||||
// 如果冻结信息不存在,返回 undefined
|
||||
if (!freeze) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前工作表的单元格矩阵打印范围
|
||||
const cellMatrixPrintRange = worksheet?.getCellMatrixPrintRange();
|
||||
|
||||
// 如果没有获取到单元格矩阵打印范围,返回 undefined
|
||||
if (!cellMatrixPrintRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前工作表骨架中的溢出缓存数据
|
||||
const { overflowCache } = skeleton;
|
||||
if (overflowCache) {
|
||||
// 如果有溢出缓存,遍历其值并调整打印范围
|
||||
overflowCache.forValue((_row: any, _col: any, value: any) => {
|
||||
const { endColumn } = value;
|
||||
|
||||
// 如果溢出的单元格列超过当前打印范围的结束列,则调整打印范围
|
||||
if (endColumn > cellMatrixPrintRange?.endColumn) {
|
||||
cellMatrixPrintRange.endColumn = endColumn;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 使用拦截器调整打印范围
|
||||
const realCellMatrixPrintRange = this._sheetPrintInterceptorService.interceptor?.fetchThroughInterceptors(
|
||||
this._sheetPrintInterceptorService.interceptor?.getInterceptPoints()?.PRINTING_RANGE,
|
||||
)(cellMatrixPrintRange, { subUnitId, unitId });
|
||||
|
||||
// 返回最终计算出的打印页面范围,包括冻结行列的调整
|
||||
return this._computePrintPageRange(subUnitId, {
|
||||
// 调整起始行列位置,确保不小于零
|
||||
...realCellMatrixPrintRange,
|
||||
startColumn: Math.max(freeze.startColumn - freeze.xSplit, 0),
|
||||
startRow: Math.max(freeze.startRow - freeze.ySplit, 0),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置的纸张边距信息。
|
||||
* @returns 返回包含边距信息的对象,或者返回 `null`。
|
||||
*/
|
||||
private _getPaperPadding() {
|
||||
// 从配置参数中解构获取边距类型(padding)
|
||||
const { padding } = this._configParams ?? {};
|
||||
|
||||
// 根据边距类型从预定义的边距配置映射中查找对应的边距值
|
||||
const paperPadding = JnpfPaperPaddingForType[padding as keyof typeof JnpfPaperPaddingForType] ?? {};
|
||||
|
||||
// 如果找到有效的边距配置,返回边距配置;否则返回 null
|
||||
return isEmptyObject(paperPadding) ? null : paperPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前配置的纸张大小。
|
||||
* @returns 返回包含宽度和高度的纸张尺寸对象,或者返回 `null`。
|
||||
*/
|
||||
private _getPaperSize() {
|
||||
// 从配置参数中解构获取纸张类型(paperType)和方向(direction)
|
||||
const { direction, paperType } = this._configParams ?? {};
|
||||
|
||||
// 根据纸张类型获取对应的尺寸数据
|
||||
const paperSize = JnpfPrintPaperSizeForType[paperType as keyof typeof JnpfPrintPaperSizeForType] ?? {};
|
||||
// 如果纸张尺寸为空,返回 null
|
||||
if (isEmptyObject(paperSize)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据打印方向返回调整后的纸张尺寸
|
||||
return direction === JnpfPrintDirectionEnum?.portrait
|
||||
? { ...paperSize } // 如果是纵向模式,直接返回原始纸张尺寸
|
||||
: {
|
||||
h: paperSize?.w,
|
||||
w: paperSize?.h, // 如果是横向模式,交换宽度和高度
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定单元的骨架信息。
|
||||
* @param {string} unitId - 单元的唯一标识符。
|
||||
* @param {string} subUnitId - 子单元的唯一标识符。
|
||||
* @returns 返回单元骨架信息对象,如果未找到则返回空对象。
|
||||
*/
|
||||
private _getUnitSkeleton(unitId: string, subUnitId: string) {
|
||||
// 从 `RenderManagerService` 中获取指定单元 ID 的渲染服务
|
||||
return (this._renderManagerService
|
||||
?.getRenderById(unitId) // 根据单元 ID 获取对应的渲染服务
|
||||
?.with(SheetSkeletonManagerService) // 使用骨架管理服务进行扩展
|
||||
?.getUnitSkeleton(unitId, subUnitId)?.skeleton ?? {}) as any; // 获取单元对应的骨架信息 // 如果骨架信息不存在,则返回空对象 // 强制将返回值类型设置为 `any`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活动的工作簿实例。
|
||||
* @returns 返回当前活动的工作簿实例,如果不存在则返回 `undefined`。
|
||||
*/
|
||||
private _getWorkbook() {
|
||||
// 使用 `UniverInstanceService` 获取当前类型为 `UNIVER_SHEET` 的活动单元实例
|
||||
return this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { Disposable, ICommandService, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
|
||||
import { SetSelectionsOperation } from '@univerjs/sheets';
|
||||
|
||||
export class ZqSheetsRangeService extends Disposable {
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
// 恢复选区
|
||||
public recoveryRange(row: number, col: number) {
|
||||
const unit = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
const unitId = unit!.getUnitId();
|
||||
const sheet = unit!.getActiveSheet();
|
||||
const subUnitId = sheet?.getSheetId();
|
||||
|
||||
this._commandService
|
||||
.executeCommand(SetSelectionsOperation.id, {
|
||||
selections: [
|
||||
{
|
||||
range: {
|
||||
endColumn: col,
|
||||
endRow: row,
|
||||
rangeType: 0,
|
||||
startColumn: col,
|
||||
startRow: row,
|
||||
},
|
||||
},
|
||||
],
|
||||
subUnitId,
|
||||
type: 2,
|
||||
unitId,
|
||||
})
|
||||
.then();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
export type CodeDisplayType = 'default' | 'jsbarcode' | 'qrCode';
|
||||
|
||||
export interface CodeCellTarget {
|
||||
col: number;
|
||||
content: string;
|
||||
custom: Record<string, any>;
|
||||
displayType: 'jsbarcode' | 'qrCode';
|
||||
row: number;
|
||||
}
|
||||
|
||||
export function resolveCodeDisplayType(custom: Record<string, any> | undefined): CodeDisplayType {
|
||||
if (!custom) {
|
||||
return 'default';
|
||||
}
|
||||
if (custom.type === 'qrCode') {
|
||||
return 'qrCode';
|
||||
}
|
||||
if (custom.type === 'jsbarcode') {
|
||||
return 'jsbarcode';
|
||||
}
|
||||
const dt = String(custom.displayType || 'default');
|
||||
if (dt === 'qrCode' || dt === 'jsbarcode') {
|
||||
return dt;
|
||||
}
|
||||
return 'default';
|
||||
}
|
||||
|
||||
export function resolveCodeContent(custom: Record<string, any>, v: unknown): string {
|
||||
const raw = v ?? custom?.field ?? '';
|
||||
if (raw === null || raw === undefined) {
|
||||
return '';
|
||||
}
|
||||
const text = String(raw);
|
||||
if (custom?.type === 'dataSource') {
|
||||
const m = text.match(/^\$\{(.+)\}$/);
|
||||
if (m) {
|
||||
return m[1] || custom.field || text;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function scanCodeCellTargets(
|
||||
matrix: Record<string, any> | null | undefined,
|
||||
): CodeCellTarget[] {
|
||||
if (!matrix) {
|
||||
return [];
|
||||
}
|
||||
const targets: CodeCellTarget[] = [];
|
||||
Object.entries(matrix).forEach(([rowStr, colObj]) => {
|
||||
const row = Number(rowStr);
|
||||
if (!colObj || typeof colObj !== 'object') {
|
||||
return;
|
||||
}
|
||||
Object.entries(colObj as Record<string, any>).forEach(([colStr, cellData]) => {
|
||||
if (!cellData || typeof cellData !== 'object') {
|
||||
return;
|
||||
}
|
||||
const custom = cellData.custom || {};
|
||||
const displayType = resolveCodeDisplayType(custom);
|
||||
if (displayType !== 'qrCode' && displayType !== 'jsbarcode') {
|
||||
return;
|
||||
}
|
||||
const content = resolveCodeContent(custom, cellData.v);
|
||||
if (!content.trim()) {
|
||||
return;
|
||||
}
|
||||
targets.push({
|
||||
row,
|
||||
col: Number(colStr),
|
||||
cellData,
|
||||
custom,
|
||||
displayType,
|
||||
content,
|
||||
});
|
||||
});
|
||||
});
|
||||
return targets;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/** 插入悬浮图片时的默认占位图(无品牌标识) */
|
||||
const DEFAULT_FLOAT_IMAGE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300" role="img" aria-label="image placeholder">
|
||||
<defs>
|
||||
<linearGradient id="zq-float-img-bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#f8fafc"/>
|
||||
<stop offset="100%" stop-color="#e2e8f0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="300" height="300" fill="url(#zq-float-img-bg)"/>
|
||||
<rect x="48" y="56" width="204" height="168" rx="12" fill="#ffffff" stroke="#94a3b8" stroke-width="2" stroke-dasharray="10 8"/>
|
||||
<circle cx="108" cy="118" r="20" fill="#cbd5e1"/>
|
||||
<path d="M72 200 L128 148 L176 172 L228 124 L252 200 Z" fill="#94a3b8"/>
|
||||
</svg>`;
|
||||
|
||||
export const DEFAULT_FLOAT_IMAGE_SRC = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
|
||||
DEFAULT_FLOAT_IMAGE_SVG,
|
||||
)}`;
|
||||
|
||||
export const DefaultFloatImageOption = {
|
||||
alt: '',
|
||||
src: DEFAULT_FLOAT_IMAGE_SRC,
|
||||
};
|
||||
@@ -0,0 +1,790 @@
|
||||
import type { ITextWatermarkConfig } from '@univerjs/engine-render';
|
||||
|
||||
export const ZqCommandIds = {
|
||||
downloadExcelFile: 'jnpf-sheets.operation.download-excel-file',
|
||||
excelFileOperations: 'jnpf-sheets.operation.excel-file',
|
||||
floatEchartOperations: 'jnpf-sheets.operation.float-echart',
|
||||
focusFloatEchart: 'jnpf-sheets.operation.focus-float-echart',
|
||||
focusFloatImage: 'jnpf-sheets.operation.focus-float-image',
|
||||
importCsvFile: 'jnpf-sheets.operation.import-csv-file',
|
||||
importExcelFile: 'jnpf-sheets.operation.import-excel-file',
|
||||
|
||||
insertedFloatEchart: 'jnpf-sheets.operation.inserted-float-echart',
|
||||
insertedFloatImage: 'jnpf-sheets.operation.inserted-float-image',
|
||||
insertFloatBarEchart: 'jnpf-sheets.operation.insert-float-bar-echart',
|
||||
|
||||
insertFloatImage: 'jnpf-sheets.operation.insert-float-image',
|
||||
insertFloatLineEchart: 'jnpf-sheets.operation.insert-float-line-echart',
|
||||
insertFloatPieEchart: 'jnpf-sheets.operation.insert-float-pie-echart',
|
||||
insertFloatRadarEchart: 'jnpf-sheets.operation.insert-float-radar-echart',
|
||||
|
||||
preview: 'jnpf-sheets.operation.preview',
|
||||
print: 'jnpf-sheets.operation.print',
|
||||
};
|
||||
|
||||
export const DefaultFloatEchartWidth = 300;
|
||||
export const DefaultFloatEchartHeight = 300;
|
||||
|
||||
export const DefaultFloatImageWidth = 300;
|
||||
export const DefaultFloatImageHeight = 300;
|
||||
|
||||
/** ZQ 单体报表服务 Excel 导入 API(JNPF 走 reportHttp 微服务) */
|
||||
export const ZQ_REPORT_IMPORT_EXCEL_URL =
|
||||
'/api/online_dev/report/data/import-excel';
|
||||
|
||||
export const ZqUniverFloatEchartKey = 'ZqUniverFloatEchart';
|
||||
export const ZqUniverFloatImageKey = 'ZqUniverFloatImage';
|
||||
export const ZqUniverDialogSelectCellKey = 'ZqUniverDialogSelectCell';
|
||||
|
||||
// 等待废弃
|
||||
export const SheetMainFillDirectionExtensionKey = 'SheetMainFillDirectionExtension';
|
||||
export const SheetMainRelationCellExtensionKey = 'SheetMainRelationCellExtension';
|
||||
|
||||
export const DefaultDialogSelectCellConfig = {
|
||||
closable: false,
|
||||
destroyOnClose: false,
|
||||
draggable: true,
|
||||
maskClosable: false,
|
||||
mask: false,
|
||||
preservePositionOnDestroy: true,
|
||||
title: { title: '请选择单元格' },
|
||||
width: 326,
|
||||
};
|
||||
|
||||
export const DefaultWatermarkConfig: ITextWatermarkConfig = {
|
||||
bold: false,
|
||||
color: '#000000',
|
||||
content: 'ZQ',
|
||||
direction: 'inherit',
|
||||
fontSize: 16,
|
||||
italic: false,
|
||||
opacity: 0.1,
|
||||
repeat: true,
|
||||
rotate: -30,
|
||||
spacingX: 200, // X 轴间距
|
||||
spacingY: 150, // Y 轴间距
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
|
||||
export enum JnpfPrintAreaEnum {
|
||||
currentSelection = 'currentSelection',
|
||||
currentSheet = 'currentSheet',
|
||||
partSheets = 'partSheets',
|
||||
workbook = 'workbook',
|
||||
}
|
||||
export const jnpfPrintAreaOptions = [
|
||||
// { fullName: '所有工作表', id: JnpfPrintAreaEnum.workbook },
|
||||
// { fullName: '部分工作表', id: JnpfPrintAreaEnum.partSheets },
|
||||
{ fullName: '当前工作表', id: JnpfPrintAreaEnum.currentSheet },
|
||||
];
|
||||
|
||||
export enum JnpfPaperTypeEnum {
|
||||
a3 = 'a3',
|
||||
a4 = 'a4',
|
||||
a5 = 'a5',
|
||||
b4 = 'b4',
|
||||
b5 = 'b5',
|
||||
executive = 'executive',
|
||||
folio = 'folio',
|
||||
legal = 'legal',
|
||||
letter = 'letter',
|
||||
statement = 'statement',
|
||||
tabloid = 'tabloid',
|
||||
}
|
||||
export const jnpfPaperTypeOptions = [
|
||||
{
|
||||
fullName: 'A3(29.7厘米 x 42.0厘米)',
|
||||
id: JnpfPaperTypeEnum.a3,
|
||||
},
|
||||
{
|
||||
fullName: 'A4(21.0厘米 x 29.7厘米)',
|
||||
id: JnpfPaperTypeEnum.a4,
|
||||
},
|
||||
{
|
||||
fullName: 'A5(14.8厘米 x 21.0厘米)',
|
||||
id: JnpfPaperTypeEnum.a5,
|
||||
},
|
||||
{
|
||||
fullName: 'B4(25.0厘米 x 35.3厘米)',
|
||||
id: JnpfPaperTypeEnum.b4,
|
||||
},
|
||||
{
|
||||
fullName: 'B5(17.6厘米 x 25.0厘米)',
|
||||
id: JnpfPaperTypeEnum.b5,
|
||||
},
|
||||
{
|
||||
fullName: '信纸(21.6厘米 x 27.9厘米)',
|
||||
id: JnpfPaperTypeEnum.letter,
|
||||
},
|
||||
{
|
||||
fullName: '行政公文纸(18.4厘米 x 26.7厘米)',
|
||||
id: JnpfPaperTypeEnum.executive,
|
||||
},
|
||||
{
|
||||
fullName: '报表(14.0厘米 x 21.6厘米)',
|
||||
id: JnpfPaperTypeEnum.statement,
|
||||
},
|
||||
];
|
||||
export const printA4PaperWidth = 794;
|
||||
export const printA4PaperHeight = 1124;
|
||||
export const JnpfPrintPaperSizeForType = {
|
||||
[JnpfPaperTypeEnum.a3]: { h: 1587, w: 1123 },
|
||||
[JnpfPaperTypeEnum.a4]: { h: printA4PaperHeight, w: printA4PaperWidth },
|
||||
[JnpfPaperTypeEnum.a5]: { h: 794, w: 559 },
|
||||
[JnpfPaperTypeEnum.b4]: { h: 1344, w: 944 },
|
||||
[JnpfPaperTypeEnum.b5]: { h: 944, w: 665 },
|
||||
[JnpfPaperTypeEnum.executive]: { h: 1008, w: 696 },
|
||||
[JnpfPaperTypeEnum.folio]: { h: 1248, w: 816 },
|
||||
[JnpfPaperTypeEnum.legal]: { h: 1344, w: 816 },
|
||||
[JnpfPaperTypeEnum.letter]: { h: 1056, w: 816 },
|
||||
[JnpfPaperTypeEnum.statement]: { h: 816, w: 528 },
|
||||
[JnpfPaperTypeEnum.tabloid]: { h: 1632, w: 1056 },
|
||||
};
|
||||
|
||||
export enum JnpfPrintDirectionEnum {
|
||||
landscape = 'landscape',
|
||||
portrait = 'portrait',
|
||||
}
|
||||
export const jnpfPrintDirectionOptions = [
|
||||
{
|
||||
fullName: '纵向',
|
||||
id: JnpfPrintDirectionEnum.portrait,
|
||||
},
|
||||
{
|
||||
fullName: '横向',
|
||||
id: JnpfPrintDirectionEnum.landscape,
|
||||
},
|
||||
];
|
||||
|
||||
export enum JnpfPrintScaleEnum {
|
||||
fitHeight = 'fitHeight',
|
||||
fitPage = 'fitPage',
|
||||
fitWidth = 'fitWidth',
|
||||
origin = 'origin',
|
||||
}
|
||||
export const jnpfPrintScaleOptions = [
|
||||
{
|
||||
fullName: '正常(100%)',
|
||||
id: JnpfPrintScaleEnum.origin,
|
||||
},
|
||||
{
|
||||
fullName: '适合宽度',
|
||||
id: JnpfPrintScaleEnum.fitWidth,
|
||||
},
|
||||
{
|
||||
fullName: '适合高度',
|
||||
id: JnpfPrintScaleEnum.fitHeight,
|
||||
},
|
||||
{
|
||||
fullName: '适合页面大小',
|
||||
id: JnpfPrintScaleEnum.fitPage,
|
||||
},
|
||||
];
|
||||
|
||||
export enum JnpfPrintAlignEnum {
|
||||
end = 'end',
|
||||
middle = 'middle',
|
||||
start = 'start',
|
||||
}
|
||||
export const jnpfPrintVAlignOptions = [
|
||||
{
|
||||
fullName: '顶部对齐',
|
||||
id: JnpfPrintAlignEnum.start,
|
||||
},
|
||||
{
|
||||
fullName: '垂直居中',
|
||||
id: JnpfPrintAlignEnum.middle,
|
||||
},
|
||||
{
|
||||
fullName: '底部对齐',
|
||||
id: JnpfPrintAlignEnum.end,
|
||||
},
|
||||
];
|
||||
export const jnpfPrintHAlignOptions = [
|
||||
{
|
||||
fullName: '左对齐',
|
||||
id: JnpfPrintAlignEnum.start,
|
||||
},
|
||||
{
|
||||
fullName: '水平居中',
|
||||
id: JnpfPrintAlignEnum.middle,
|
||||
},
|
||||
{
|
||||
fullName: '右对齐',
|
||||
id: JnpfPrintAlignEnum.end,
|
||||
},
|
||||
];
|
||||
|
||||
export enum JnpfPaperPaddingTypeEnum {
|
||||
narrow = 'narrow',
|
||||
normal = 'normal',
|
||||
wide = 'wide',
|
||||
}
|
||||
export const jnpfPaperPaddingTypeOptions = [
|
||||
{
|
||||
fullName: '正常',
|
||||
id: JnpfPaperPaddingTypeEnum.normal,
|
||||
},
|
||||
{
|
||||
fullName: '窄',
|
||||
id: JnpfPaperPaddingTypeEnum.narrow,
|
||||
},
|
||||
{
|
||||
fullName: '宽',
|
||||
id: JnpfPaperPaddingTypeEnum.wide,
|
||||
},
|
||||
];
|
||||
export const JnpfPaperPaddingForType = {
|
||||
[JnpfPaperPaddingTypeEnum.narrow]: { bottom: 56, left: 19, right: 19, top: 56 },
|
||||
[JnpfPaperPaddingTypeEnum.normal]: { bottom: 56, left: 53, right: 53, top: 56 },
|
||||
[JnpfPaperPaddingTypeEnum.wide]: { bottom: 75, left: 75, right: 75, top: 75 },
|
||||
};
|
||||
|
||||
export enum JnpfPrintFreezeEnum {
|
||||
column = 'column',
|
||||
row = 'row',
|
||||
}
|
||||
|
||||
export enum JnpfPrintOtherParamsEnum {
|
||||
pageNumber = 'pageNumber',
|
||||
printDate = 'printDate',
|
||||
printTime = 'printTime',
|
||||
workbookTitle = 'workbookTitle',
|
||||
worksheetTitle = 'worksheetTitle',
|
||||
}
|
||||
|
||||
export enum JnpfPrintCustomHeaderEnum {
|
||||
centerTop = 'centerTop',
|
||||
leftTop = 'leftTop',
|
||||
rightTop = 'rightTop',
|
||||
}
|
||||
|
||||
export enum JnpfPrintCustomFooterEnum {
|
||||
centerBottom = 'centerBottom',
|
||||
leftBottom = 'leftBottom',
|
||||
rightBottom = 'rightBottom',
|
||||
}
|
||||
|
||||
// 打印默认的配置
|
||||
export const DefaultPrintConfig = {
|
||||
customHeaderFooterValue: {
|
||||
centerBottom: '',
|
||||
centerTop: '',
|
||||
leftBottom: '',
|
||||
leftTop: '',
|
||||
rightBottom: '',
|
||||
rightTop: '',
|
||||
},
|
||||
direction: JnpfPrintDirectionEnum.portrait, // 页面方向
|
||||
gridlines: true, // 网格线
|
||||
hAlign: JnpfPrintAlignEnum.middle, // 水平对齐方式
|
||||
headerFooterType: 'default', // 页眉页脚类型
|
||||
|
||||
padding: JnpfPaperPaddingTypeEnum.normal, // 边距
|
||||
pageNumber: false, // 页码
|
||||
|
||||
paperType: JnpfPaperTypeEnum.a4, // 纸张类型
|
||||
|
||||
partSheets: [], // 部分工作表
|
||||
printArea: JnpfPrintAreaEnum.currentSheet, // 打印范围
|
||||
printDate: false, // 当前日期
|
||||
|
||||
printScale: JnpfPrintScaleEnum.origin, // 缩放
|
||||
printTime: false, // 当前时间
|
||||
|
||||
vAlign: JnpfPrintAlignEnum.start, // 垂直对齐方式
|
||||
|
||||
workbookTitle: false, // 是否显示工作簿标题
|
||||
workbookTitleText: '', // 工作簿标题文本
|
||||
|
||||
worksheetTitle: false, // 工作本名称
|
||||
|
||||
xFreeze: false, // 重复冻结列
|
||||
yFreeze: false, // 重复冻结行
|
||||
};
|
||||
|
||||
export { DefaultFloatImageOption } from './default-float-image';
|
||||
|
||||
export const DefaultFloatEchartOptions = {
|
||||
bar: {
|
||||
animation: false,
|
||||
bar: { barTypeList: [] },
|
||||
barWidth: 25, // 宽度设置
|
||||
chartType: 'bar',
|
||||
classifyNameField: undefined,
|
||||
// 自定义配色
|
||||
color: {
|
||||
AxisLineStyleColor: '', // X、Y轴轴线颜色全局设置
|
||||
AxisTextStyleColor: '', // X、Y轴字体的颜色全局设置
|
||||
list: [],
|
||||
},
|
||||
// 坐标轴边距设置
|
||||
grid: {
|
||||
bottom: 50,
|
||||
left: 30,
|
||||
right: 10,
|
||||
top: 60,
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 0, // 弧度设置
|
||||
},
|
||||
// 数值设置
|
||||
label: {
|
||||
backgroundColor: '', // 数值背景色
|
||||
color: '#303133', // 数值颜色
|
||||
fontSize: 14, // 数值大小
|
||||
fontWeight: '', // 数值加粗
|
||||
seriesCenterLeft: 50,
|
||||
seriesCenterTop: 50,
|
||||
seriesLabelPosition: 'outside',
|
||||
seriesLabelShowInfo: ['count', 'percent'],
|
||||
seriesLineStyleWidth: 2,
|
||||
seriesSymbolRotate: 4,
|
||||
show: false, // 显示数值
|
||||
},
|
||||
// 图例设置
|
||||
legend: {
|
||||
fontSize: 14,
|
||||
left: '40%',
|
||||
orient: 'horizontal',
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
top: '90%',
|
||||
},
|
||||
legendLeft: 40,
|
||||
legendTop: 90,
|
||||
series: [
|
||||
{
|
||||
data: [5, 20, 36, 10, 10, 20],
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
},
|
||||
{
|
||||
data: [10, 30, 90, 30, 30, 80],
|
||||
name: '销量1',
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
seriesDataField: undefined,
|
||||
seriesNameField: undefined,
|
||||
styleType: 1, // 风格类型
|
||||
summaryType: 'none',
|
||||
// 标题设置
|
||||
title: {
|
||||
left: 'center',
|
||||
show: true,
|
||||
showSub: true,
|
||||
subtext: '子标题',
|
||||
subtextI18nCode: '',
|
||||
subtextStyle: {
|
||||
color: '#aaa',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
text: '柱状图示例',
|
||||
textI18nCode: '',
|
||||
textStyle: { color: '#333', fontSize: 18, fontWeight: 'normal' },
|
||||
},
|
||||
// 提示语设置
|
||||
tooltip: {
|
||||
backgroundColor: '#fff',
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#303133',
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
color: '#303133', // X轴标签字体颜色
|
||||
fontSize: 14, // X轴标签字体大小
|
||||
fontWeight: '', // X轴标签是否加粗
|
||||
rotate: 0, // X轴标签角度
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#303133', // X轴线颜色
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
axisTick: { alignWithLabel: false }, // 刻度对齐
|
||||
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
|
||||
inverse: false, // X轴反转
|
||||
name: '', // X轴名称
|
||||
nameTextStyle: {
|
||||
color: '#303133', // X轴名称字体颜色
|
||||
fontSize: 14, // X轴名称字体大小
|
||||
fontWeight: '', // X轴名称是否加粗
|
||||
},
|
||||
show: true, // X轴是否显示
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#DFDFDF', // X轴网格线颜色
|
||||
},
|
||||
show: false, // X轴网格线
|
||||
},
|
||||
type: 'category', // X轴类型[category,value]
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
color: '#303133', // y轴标签字体颜色
|
||||
fontSize: 14, // y轴标签字体大小
|
||||
fontWeight: '', // y轴标签是否加粗
|
||||
rotate: 0, // y轴标签角度
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#303133', // y轴线颜色
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
inverse: false, // y轴反转
|
||||
name: '', // y轴名称
|
||||
nameTextStyle: {
|
||||
color: '#303133', // y轴名称字体颜色
|
||||
fontSize: 14, // y轴名称字体大小
|
||||
fontWeight: '', // y轴名称是否加粗
|
||||
},
|
||||
show: true, // y轴是否显示
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#DFDFDF', // y轴网格线颜色
|
||||
},
|
||||
show: false, // y轴网格线
|
||||
},
|
||||
},
|
||||
},
|
||||
line: {
|
||||
animation: false,
|
||||
areaStyle: false, // 面积堆积
|
||||
chartType: 'line',
|
||||
classifyNameField: undefined,
|
||||
// 自定义配色
|
||||
color: {
|
||||
AxisLineStyleColor: '', // X、Y轴轴线颜色全局设置
|
||||
AxisTextStyleColor: '', // X、Y轴字体的颜色全局设置
|
||||
list: [],
|
||||
},
|
||||
// 坐标轴边距设置
|
||||
grid: {
|
||||
bottom: 50,
|
||||
left: 30,
|
||||
right: 10,
|
||||
top: 60,
|
||||
},
|
||||
label: {
|
||||
backgroundColor: '', // 数值背景色
|
||||
color: '#303133', // 数值颜色
|
||||
fontSize: 14, // 数值大小
|
||||
fontWeight: '', // 数值加粗
|
||||
show: false, // 显示数值
|
||||
},
|
||||
// 图例设置
|
||||
legend: {
|
||||
fontSize: 14,
|
||||
left: '40%',
|
||||
orient: 'horizontal',
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
top: '90%',
|
||||
},
|
||||
legendLeft: 40,
|
||||
legendTop: 90,
|
||||
line: {
|
||||
smooth: false,
|
||||
symbolSize: 4, // 点的大小
|
||||
},
|
||||
lineStyle: {
|
||||
Width: 2, // 线条宽度
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [5, 20, 36, 10, 10, 20],
|
||||
name: '销量',
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
data: [10, 30, 90, 30, 30, 80],
|
||||
name: '销量1',
|
||||
type: 'line',
|
||||
},
|
||||
],
|
||||
seriesDataField: undefined,
|
||||
seriesNameField: undefined,
|
||||
styleType: 1, // 风格类型.
|
||||
summaryType: 'none',
|
||||
// 标题设置
|
||||
title: {
|
||||
left: 'center',
|
||||
show: true,
|
||||
showSub: true,
|
||||
subtext: '子标题',
|
||||
subtextI18nCode: '',
|
||||
subtextStyle: {
|
||||
color: '#aaa',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
text: '折线图示例',
|
||||
textI18nCode: '',
|
||||
textStyle: { color: '#333', fontSize: 18, fontWeight: 'normal' },
|
||||
},
|
||||
// 提示语设置
|
||||
tooltip: {
|
||||
backgroundColor: '#fff',
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#303133',
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
color: '#303133', // X轴标签字体颜色
|
||||
fontSize: 14, // X轴标签字体大小
|
||||
fontWeight: '', // X轴标签是否加粗
|
||||
rotate: 0, // X轴标签角度
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#303133', // X轴线颜色
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
axisTick: { alignWithLabel: false }, // 刻度对齐
|
||||
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
|
||||
inverse: false, // X轴反转
|
||||
name: '', // X轴名称
|
||||
nameTextStyle: {
|
||||
color: '#303133', // X轴名称字体颜色
|
||||
fontSize: 14, // X轴名称字体大小
|
||||
fontWeight: '', // X轴名称是否加粗
|
||||
},
|
||||
show: true, // X轴是否显示
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#DFDFDF', // X轴网格线颜色
|
||||
},
|
||||
show: false, // X轴网格线
|
||||
},
|
||||
type: 'category', // X轴类型[category,value]
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
color: '#303133', // y轴标签字体颜色
|
||||
fontSize: 14, // y轴标签字体大小
|
||||
fontWeight: '', // y轴标签是否加粗
|
||||
rotate: 0, // y轴标签角度
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#303133', // y轴线颜色
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
inverse: false, // y轴反转
|
||||
name: '', // y轴名称
|
||||
nameTextStyle: {
|
||||
color: '#303133', // y轴名称字体颜色
|
||||
fontSize: 14, // y轴名称字体大小
|
||||
fontWeight: '', // y轴名称是否加粗
|
||||
},
|
||||
show: true, // y轴是否显示
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#DFDFDF', // y轴网格线颜色
|
||||
},
|
||||
show: false, // y轴网格线
|
||||
},
|
||||
},
|
||||
},
|
||||
pie: {
|
||||
animation: false,
|
||||
chartType: 'pie',
|
||||
classifyNameField: undefined,
|
||||
// 自定义配色
|
||||
color: {
|
||||
AxisLineStyleColor: '', // X、Y轴轴线颜色全局设置
|
||||
AxisTextStyleColor: '', // X、Y轴字体的颜色全局设置
|
||||
list: [],
|
||||
},
|
||||
label: {
|
||||
backgroundColor: '', // 数值背景色
|
||||
color: '#303133', // 数值颜色
|
||||
fontSize: 14, // 数值大小
|
||||
fontWeight: '', // 数值加粗
|
||||
formatter: '{b}: {c} ({d}%)',
|
||||
show: true, // 显示数值
|
||||
},
|
||||
// 图例设置
|
||||
legend: {
|
||||
fontSize: 14,
|
||||
orient: 'horizontal',
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
top: 'bottom',
|
||||
},
|
||||
pie: { roseType: false },
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{ name: '衬衫', value: 5 },
|
||||
{ name: '羊毛衫', value: 20 },
|
||||
{ name: '雪纺衫', value: 36 },
|
||||
{ name: '裤子', value: 10 },
|
||||
{ name: '高跟鞋', value: 10 },
|
||||
{ name: '袜子', value: 20 },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
shadowOffsetX: 0,
|
||||
},
|
||||
},
|
||||
label: {
|
||||
formatter: '{b}: {c} ({d}%)',
|
||||
show: true, // 显示数值
|
||||
},
|
||||
name: '衣物',
|
||||
radius: '50%',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
seriesCenter: {
|
||||
seriesCenterLeft: 50,
|
||||
seriesCenterTop: 50,
|
||||
},
|
||||
seriesDataField: undefined,
|
||||
seriesLabelPosition: 'outside',
|
||||
seriesLabelShowInfo: ['count', 'percent'],
|
||||
seriesNameField: undefined,
|
||||
styleType: 1, // 风格类型
|
||||
summaryType: 'none',
|
||||
// 标题设置
|
||||
title: {
|
||||
left: 'center',
|
||||
show: true,
|
||||
showSub: true,
|
||||
subtext: '子标题',
|
||||
subtextI18nCode: '',
|
||||
subtextStyle: {
|
||||
color: '#aaa',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
text: '饼图示例',
|
||||
textI18nCode: '',
|
||||
textStyle: { color: '#333', fontSize: 18, fontWeight: 'normal' },
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: '#fff',
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#303133',
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
},
|
||||
radar: {
|
||||
animation: false,
|
||||
classifyNameField: undefined,
|
||||
// 自定义配色
|
||||
color: {
|
||||
AxisLineStyleColor: '', // X、Y轴轴线颜色全局设置
|
||||
AxisTextStyleColor: '', // X、Y轴字体的颜色全局设置
|
||||
list: [],
|
||||
},
|
||||
label: {
|
||||
backgroundColor: '', // 数值背景色
|
||||
color: '#303133', // 数值颜色
|
||||
fontSize: 14, // 数值大小
|
||||
fontWeight: '', // 数值加粗
|
||||
show: false, // 显示数值
|
||||
},
|
||||
// 图例设置
|
||||
legend: {
|
||||
fontSize: 14,
|
||||
orient: 'horizontal',
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
top: 'bottom',
|
||||
},
|
||||
maxField: undefined,
|
||||
radar: {
|
||||
axisName: {
|
||||
color: '#303133',
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
indicator: [
|
||||
{ max: 6500, name: '销售' },
|
||||
{ max: 16_000, name: '行政管理' },
|
||||
{ max: 30_000, name: '信息技术' },
|
||||
{ max: 38_000, name: '客户支持' },
|
||||
{ max: 52_000, name: '开发' },
|
||||
{ max: 25_000, name: '市场营销' },
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: { opacity: 0.3 },
|
||||
data: [
|
||||
{
|
||||
name: '分配预算',
|
||||
value: [4200, 3000, 20_000, 35_000, 50_000, 18_000],
|
||||
},
|
||||
],
|
||||
name: '预算',
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
seriesAreaStyleOpacity: 0.3,
|
||||
seriesCenter: {
|
||||
seriesCenterLeft: 50,
|
||||
seriesCenterTop: 50,
|
||||
},
|
||||
seriesDataField: undefined,
|
||||
seriesNameField: undefined,
|
||||
styleType: 1, // 风格类型
|
||||
summaryType: 'none',
|
||||
// 标题设置
|
||||
title: {
|
||||
left: 'center',
|
||||
show: true,
|
||||
showSub: true,
|
||||
subtext: '子标题',
|
||||
subtextI18nCode: '',
|
||||
subtextStyle: {
|
||||
color: '#aaa',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
text: '雷达图示例',
|
||||
textI18nCode: '',
|
||||
textStyle: { color: '#333', fontSize: 18, fontWeight: 'normal' },
|
||||
},
|
||||
// 提示语设置
|
||||
tooltip: {
|
||||
backgroundColor: '#fff',
|
||||
show: true,
|
||||
textStyle: {
|
||||
color: '#303133',
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 报表表达式单元格(对齐 JNPF:f=公式串,t=4 强制字符串展示)
|
||||
*/
|
||||
export function normalizeReportFormula(raw: string): string {
|
||||
const text = (raw || '').trim();
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
return text.startsWith('=') ? text : `=${text}`;
|
||||
}
|
||||
|
||||
export function buildExpressionCellData(
|
||||
oldCellData: Record<string, any>,
|
||||
custom: Record<string, any>,
|
||||
rawFormula: string,
|
||||
): Record<string, any> {
|
||||
const formula = normalizeReportFormula(
|
||||
custom?.formula || custom?.field || rawFormula,
|
||||
);
|
||||
const bare = formula.startsWith('=') ? formula.slice(1) : formula;
|
||||
return {
|
||||
...oldCellData,
|
||||
custom: {
|
||||
...oldCellData?.custom,
|
||||
...custom,
|
||||
type: 'expression',
|
||||
field: custom?.field || bare,
|
||||
formula: custom?.formula || formula,
|
||||
},
|
||||
t: 4,
|
||||
f: formula,
|
||||
v: formula,
|
||||
};
|
||||
}
|
||||
|
||||
/** 加载 snapshot 时补齐历史表达式单元格的 f/t */
|
||||
export function hydrateExpressionCellsInSnapshot(snapshot: Record<string, any>) {
|
||||
if (!snapshot?.sheets) {
|
||||
return snapshot;
|
||||
}
|
||||
for (const sheet of Object.values(snapshot.sheets) as Record<string, any>[]) {
|
||||
const cellData = sheet?.cellData;
|
||||
if (!cellData) {
|
||||
continue;
|
||||
}
|
||||
for (const row of Object.values(cellData) as Record<string, any>[]) {
|
||||
for (const cell of Object.values(row) as Record<string, any>[]) {
|
||||
const custom = cell?.custom;
|
||||
if (custom?.type !== 'expression') {
|
||||
continue;
|
||||
}
|
||||
const raw =
|
||||
custom.formula ||
|
||||
custom.field ||
|
||||
cell.f ||
|
||||
(typeof cell.v === 'string' ? cell.v : '');
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
const built = buildExpressionCellData(cell, custom, raw);
|
||||
Object.assign(cell, built);
|
||||
}
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
import { JnpfPrintDirectionEnum, JnpfPrintPaperSizeForType } from './define';
|
||||
|
||||
/**
|
||||
* 检查值是否是 null 或 undefined
|
||||
* @param value 要检查的值
|
||||
* @returns 如果值是 null 或 undefined,返回 true,否则返回 false
|
||||
*/
|
||||
export const isNullOrUndefined = (value: any) => {
|
||||
return value === null || value === undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断一个值是否为空对象
|
||||
* @param obj - 待检查的值
|
||||
* @returns 如果是空对象,返回 true;否则返回 false
|
||||
*/
|
||||
export const isEmptyObject = (obj: unknown): boolean => {
|
||||
return obj !== null && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将指定值从数组中移除并将其添加到数组的末尾。
|
||||
* @param array - 要操作的数组。
|
||||
* @param value - 要移动到数组末尾的值。
|
||||
* @returns - 操作后的数组(原数组被修改)。
|
||||
*/
|
||||
export const moveArrayValueToEnd = (array: any[], value: number | string) => {
|
||||
const index = array?.indexOf(value); // 找到目标值的索引
|
||||
|
||||
if (index !== -1) {
|
||||
// 如果值存在于数组中
|
||||
const [item] = array.splice(index, 1); // 删除目标值
|
||||
array.push(item); // 添加到数组末尾
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据字母规则计算输入字符串的索引值。
|
||||
* @param input {string} - 输入的字母字符串(如 "A", "B", "AA", "AB" 等)。
|
||||
* @returns {number} - 对应的索引值。
|
||||
*/
|
||||
export const getIndexFromAlphabetRule = (input: string): null | number => {
|
||||
if (isNullOrUndefined(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const base = 26; // 字母表的长度
|
||||
const charCodeA = 'A'.charCodeAt(0);
|
||||
|
||||
let index = 0;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
index = index * base + (input.charCodeAt(i) - charCodeA + 1);
|
||||
}
|
||||
|
||||
return index - 1; // 数组索引从 0 开始
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据索引值获取字母字符串。
|
||||
* @param index {number} - 输入的索引值(如 0, 1, 26, 27 等)。
|
||||
* @returns {string} - 对应的字母字符串(如 "A", "B", "AA", "AB" 等)。
|
||||
*/
|
||||
export const getAlphabetFromIndexRule = (index: number): string => {
|
||||
const base = 26; // 字母表的长度
|
||||
const charCodeA = 'A'.charCodeAt(0);
|
||||
|
||||
let result = '';
|
||||
index += 1; // 转为从 1 开始的规则
|
||||
|
||||
while (index > 0) {
|
||||
const remainder = (index - 1) % base;
|
||||
result = String.fromCharCode(charCodeA + remainder) + result;
|
||||
index = Math.floor((index - 1) / base);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据类型和坐标获取箭头单元格数组
|
||||
* @param parentCellType - 父单元格类型
|
||||
* @param colName - 列名
|
||||
* @param rowName - 行名
|
||||
* @returns 单元格坐标数组
|
||||
*/
|
||||
// export const getSheetRelationCell = (parentCellType: string, colName?: string, rowName?: string): { row: number; col: number }[] => {
|
||||
// if (parentCellType === 'none' || !colName || !rowName) return [];
|
||||
//
|
||||
// const col = getIndexFromAlphabetRule(colName);
|
||||
// const row = Number(rowName) - 1;
|
||||
//
|
||||
// return [{ row, col }];
|
||||
// };
|
||||
|
||||
/**
|
||||
* 修正工作表中的单元格数据。
|
||||
* 根据传入的单元格数据 (`cellData`) 和指定的行、列数量,判断是否需要修正数据。
|
||||
* 如果所有单元格数据异常(如未定义、空字符串、或无效值),则修正为默认配置;
|
||||
* 如果存在正常单元格,则返回原始数据。
|
||||
*
|
||||
*/
|
||||
export const correctSheetCellData = (cellData: any = {}, rowCount: number, columnCount: number, isFloatDom: boolean = false) => {
|
||||
// 单元格纠正配置
|
||||
const correctCellDataConfig = {
|
||||
t: 1,
|
||||
v: ' ',
|
||||
};
|
||||
|
||||
// 如果 cellData 是空对象,直接返回默认初始化的单元格数据
|
||||
if (isEmptyObject(cellData)) {
|
||||
return {
|
||||
0: {
|
||||
0: correctCellDataConfig,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let totalCells = 0;
|
||||
let abnormalCells = 0;
|
||||
// 统计单元格数据中正常与异常的数量
|
||||
for (const rowKey in cellData) {
|
||||
const rowValue = cellData[rowKey] ?? {};
|
||||
for (const colKey in rowValue) {
|
||||
const { s, t, v, p } = rowValue[colKey] ?? {};
|
||||
|
||||
// 判断异常单元格条件
|
||||
if (isNullOrUndefined(s) && !p && (v === undefined || (v === '' && t === 1) || (v === 0 && (t === 2 || t === 3)))) {
|
||||
abnormalCells++;
|
||||
}
|
||||
|
||||
totalCells++;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果存在正常单元格,直接返回原始数据
|
||||
if (totalCells > abnormalCells) {
|
||||
return cellData;
|
||||
}
|
||||
|
||||
// 否则需要修正数据
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
for (let j = 0; j < columnCount; j++) {
|
||||
const { custom, t, v } = cellData?.[i]?.[j] ?? {};
|
||||
|
||||
// 判断是否需要修正(根据 isFloatDom 区分)
|
||||
const needsCorrection = isFloatDom
|
||||
? custom === undefined && (v === undefined || (v === '' && t === 1))
|
||||
: custom === undefined && (v === undefined || (v === '' && t === 1) || (v === 0 && (t === 2 || t === 3)));
|
||||
|
||||
// 如果发现第一个需要修正的单元格,修正并结束所有循环
|
||||
if (needsCorrection) {
|
||||
if (!cellData[i]) {
|
||||
cellData[i] = {};
|
||||
}
|
||||
cellData[i][j] = correctCellDataConfig;
|
||||
|
||||
return cellData; // 修正完成后直接返回
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 排查不出来问题,只能返回了
|
||||
return cellData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将 Base64 编码的字符串转换为 File 对象
|
||||
* @param base64String Base64 字符串(必须以 `data:` 开头)
|
||||
* @param fileName 生成的文件名
|
||||
* @param mimeType 文件 MIME 类型(如 "image/png", "application/pdf")
|
||||
* @returns 返回一个 Promise,解析后得到 File 对象
|
||||
*/
|
||||
export function base64ToFile(base64String: string, fileName: string, mimeType: string): Promise<File> {
|
||||
return fetch(base64String)
|
||||
.then(res => res.blob()) // 将 Base64 转换为 Blob
|
||||
.then(blob => new File([blob], fileName, { type: mimeType })); // 生成 File 对象
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算旋转后的边界框尺寸
|
||||
* @param {number} width 原始宽度
|
||||
* @param {number} height 原始高度
|
||||
* @param {number} angleDegrees 旋转角度(0-360,单位:度)
|
||||
* @returns {{ rotatedWidth: number; rotatedHeight: number }} 旋转后的宽度和高度
|
||||
*/
|
||||
export function rotatedBoundingBox(width: number, height: number, angleDegrees: number): { rotatedHeight: number; rotatedWidth: number } {
|
||||
const angle = (angleDegrees * Math.PI) / 180; // 角度转换为弧度
|
||||
const rotatedWidth = Math.abs(width * Math.cos(angle)) + Math.abs(height * Math.sin(angle));
|
||||
const rotatedHeight = Math.abs(width * Math.sin(angle)) + Math.abs(height * Math.cos(angle));
|
||||
return { rotatedHeight, rotatedWidth };
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算插入行和列后父格的位置
|
||||
* @param range - 选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
|
||||
* @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
|
||||
* @param offset - 插入的行数或列数,决定偏移量
|
||||
* @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
|
||||
*/
|
||||
export function getParentCellPosWhenInsert(range: any, axis: 'col' | 'row', offset: number, custom: any) {
|
||||
const { startColumn = 0, startRow = 0 } = range ?? {};
|
||||
|
||||
let {
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
leftParentCellType,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
topParentCellType,
|
||||
} = custom ?? {};
|
||||
|
||||
// 上父格自定义
|
||||
if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetTopColIndex = Number(topParentCellCustomColName) - 1;
|
||||
if (startRow <= targetTopColIndex) {
|
||||
topParentCellCustomColName = (targetTopColIndex + offset + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
|
||||
if (targetTopRowIndex !== null && startColumn <= targetTopRowIndex) {
|
||||
topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左父格自定义
|
||||
if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
|
||||
if (startRow <= targetLeftColIndex) {
|
||||
leftParentCellCustomColName = (targetLeftColIndex + offset + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
|
||||
if (targetLeftRowIndex !== null && startColumn <= targetLeftRowIndex) {
|
||||
leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...custom,
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算删除行和列后父格的位置
|
||||
* @param range - 选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
|
||||
* @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
|
||||
* @param offset - 插入的行数或列数,决定偏移量
|
||||
* @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
|
||||
*/
|
||||
export function getParentCellPosWhenDelete(range: any, axis: 'col' | 'row', offset: number, custom: any) {
|
||||
const { endColumn = 0, endRow = 0, startColumn = 0, startRow = 0 } = range ?? {};
|
||||
|
||||
let {
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
leftParentCellType,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
topParentCellType,
|
||||
} = custom ?? {};
|
||||
|
||||
// 上父格自定义
|
||||
if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetTopColIndex = Number(topParentCellCustomColName) - 1;
|
||||
|
||||
if (startRow <= targetTopColIndex && endRow >= targetTopColIndex) {
|
||||
topParentCellCustomColName = null;
|
||||
topParentCellCustomRowName = null;
|
||||
} else if (startRow <= targetTopColIndex) {
|
||||
topParentCellCustomColName = (targetTopColIndex - offset + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
|
||||
if (targetTopRowIndex !== null) {
|
||||
if (startColumn <= targetTopRowIndex && endColumn >= targetTopRowIndex) {
|
||||
topParentCellCustomColName = null;
|
||||
topParentCellCustomRowName = null;
|
||||
} else if (startColumn <= targetTopRowIndex) {
|
||||
topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex - offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左父格自定义
|
||||
if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
|
||||
|
||||
if (startRow <= targetLeftColIndex && endRow >= targetLeftColIndex) {
|
||||
leftParentCellCustomColName = null;
|
||||
leftParentCellCustomRowName = null;
|
||||
} else if (startRow <= targetLeftColIndex) {
|
||||
leftParentCellCustomColName = (targetLeftColIndex - offset + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
|
||||
if (targetLeftRowIndex !== null) {
|
||||
if (startColumn <= targetLeftRowIndex && endColumn >= targetLeftRowIndex) {
|
||||
leftParentCellCustomColName = null;
|
||||
leftParentCellCustomRowName = null;
|
||||
} else if (startColumn <= targetLeftRowIndex) {
|
||||
leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex - offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...custom,
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算移动行和列后父格的位置
|
||||
* @param sourceRange - 原选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
|
||||
* @param targetRange - 后影响的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
|
||||
* @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
|
||||
* @param offset - 插入的行数或列数,决定偏移量
|
||||
* @param involved - 涉及的行或列的数量
|
||||
* @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
|
||||
*/
|
||||
export function getParentCellPosWhenMove(sourceRange: any, targetRange: any, axis: 'col' | 'row', offset: number, involved: number, custom: any) {
|
||||
let {
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
leftParentCellType,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
topParentCellType,
|
||||
} = custom ?? {};
|
||||
|
||||
// 上父格自定义
|
||||
if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetTopColIndex = Number(topParentCellCustomColName) - 1;
|
||||
|
||||
if (sourceRange.startRow <= targetTopColIndex && targetTopColIndex <= sourceRange.endRow) {
|
||||
topParentCellCustomColName = (targetTopColIndex + offset + 1).toString();
|
||||
} else if (
|
||||
(sourceRange.startRow < targetTopColIndex && targetTopColIndex < targetRange.startRow) ||
|
||||
(sourceRange.startRow > targetTopColIndex && targetTopColIndex >= targetRange.startRow)
|
||||
) {
|
||||
const moveValue = offset > 0 ? involved : -involved;
|
||||
topParentCellCustomColName = (targetTopColIndex - moveValue + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
|
||||
if (targetTopRowIndex !== null) {
|
||||
if (sourceRange.startColumn <= targetTopRowIndex && targetTopRowIndex <= sourceRange.endColumn) {
|
||||
topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offset);
|
||||
} else if (
|
||||
(sourceRange.startColumn < targetTopRowIndex && targetTopRowIndex < targetRange.startColumn) ||
|
||||
(sourceRange.startColumn > targetTopRowIndex && targetTopRowIndex >= targetRange.startColumn)
|
||||
) {
|
||||
const moveValue = offset > 0 ? involved : -involved;
|
||||
topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex - moveValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左父格自定义
|
||||
if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
|
||||
// 行操作
|
||||
if (axis === 'row') {
|
||||
const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
|
||||
|
||||
if (sourceRange.startRow <= targetLeftColIndex && targetLeftColIndex <= sourceRange.endRow) {
|
||||
leftParentCellCustomColName = (targetLeftColIndex + offset + 1).toString();
|
||||
} else if (
|
||||
(sourceRange.startRow < targetLeftColIndex && targetLeftColIndex < targetRange.startRow) ||
|
||||
(sourceRange.startRow > targetLeftColIndex && targetLeftColIndex >= targetRange.startRow)
|
||||
) {
|
||||
const moveValue = offset > 0 ? involved : -involved;
|
||||
leftParentCellCustomColName = (targetLeftColIndex - moveValue + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 列操作
|
||||
if (axis === 'col') {
|
||||
const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
|
||||
if (targetLeftRowIndex !== null) {
|
||||
if (sourceRange.startColumn <= targetLeftRowIndex && targetLeftRowIndex <= sourceRange.endColumn) {
|
||||
leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offset);
|
||||
} else if (
|
||||
(sourceRange.startColumn < targetLeftRowIndex && targetLeftRowIndex < targetRange.startColumn) ||
|
||||
(sourceRange.startColumn > targetLeftRowIndex && targetLeftRowIndex >= targetRange.startColumn)
|
||||
) {
|
||||
const moveValue = offset > 0 ? involved : -involved;
|
||||
leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex - moveValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...custom,
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算移动单元格后父格的位置
|
||||
* @param {object} fromRange - 被移动的单元格的范围,包含起始和结束行列坐标
|
||||
* @param {object} offset - 行列偏移量,包含纵向(offsetRow)和横向(offsetCol)的偏移
|
||||
* @param {object} custom - 当前单元格的自定义信息,包含上父格和左父格的信息
|
||||
*/
|
||||
export function getParentCellPosWhenMoveCell(fromRange: any, offset: any, custom: any) {
|
||||
const { offsetCol, offsetRow } = offset ?? {};
|
||||
let {
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
leftParentCellType,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
topParentCellType,
|
||||
} = custom ?? {};
|
||||
|
||||
// 上父格自定义
|
||||
if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
|
||||
const targetTopColIndex = Number(topParentCellCustomColName) - 1;
|
||||
const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
|
||||
|
||||
if (targetTopRowIndex !== null) {
|
||||
const isLinchpin =
|
||||
fromRange.startRow <= targetTopColIndex &&
|
||||
targetTopColIndex <= fromRange.endRow &&
|
||||
fromRange.startColumn <= targetTopRowIndex &&
|
||||
targetTopRowIndex <= fromRange.endColumn; // 当事单元格
|
||||
|
||||
if (
|
||||
offsetRow !== 0 && // 纵向操作
|
||||
isLinchpin
|
||||
) {
|
||||
topParentCellCustomColName = (targetTopColIndex + offsetRow + 1).toString();
|
||||
}
|
||||
|
||||
if (
|
||||
offsetCol !== 0 && // 横向操作
|
||||
isLinchpin
|
||||
) {
|
||||
topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offsetCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左父格自定义
|
||||
if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
|
||||
const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
|
||||
const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
|
||||
|
||||
if (targetLeftRowIndex !== null) {
|
||||
const isLinchpin =
|
||||
fromRange.startRow <= targetLeftColIndex &&
|
||||
targetLeftColIndex <= fromRange.endRow &&
|
||||
fromRange.startColumn <= targetLeftRowIndex &&
|
||||
targetLeftRowIndex <= fromRange.endColumn; // 当事单元格
|
||||
|
||||
if (
|
||||
offsetRow !== 0 && // 纵向操作
|
||||
isLinchpin
|
||||
) {
|
||||
leftParentCellCustomColName = (targetLeftColIndex + offsetRow + 1).toString();
|
||||
}
|
||||
|
||||
if (
|
||||
offsetCol !== 0 && // 横向操作
|
||||
isLinchpin
|
||||
) {
|
||||
leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offsetCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...custom,
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算清楚单元格后父格的位置
|
||||
* @param ranges - 当前选择的单元格范围列表,每个范围包含起始和结束行列
|
||||
* @param custom - 父格的自定义信息,包括行列名称和类型
|
||||
*/
|
||||
export function getParentCellPosWhenClearCell(ranges: any, custom: any) {
|
||||
let {
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
leftParentCellType,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
topParentCellType,
|
||||
} = custom ?? {};
|
||||
|
||||
// 上父格自定义
|
||||
if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
|
||||
const targetTopColIndex = Number(topParentCellCustomColName) - 1;
|
||||
const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
|
||||
|
||||
if (targetTopRowIndex !== null) {
|
||||
const isLinchpin = ranges.some(({ endColumn, endRow, startColumn, startRow }: any) => {
|
||||
return startRow <= targetTopColIndex && targetTopColIndex <= endRow && startColumn <= targetTopRowIndex && targetTopRowIndex <= endColumn;
|
||||
});
|
||||
|
||||
if (isLinchpin) {
|
||||
topParentCellCustomColName = null;
|
||||
topParentCellCustomRowName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 左父格自定义
|
||||
if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
|
||||
const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
|
||||
const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
|
||||
|
||||
if (targetLeftRowIndex !== null) {
|
||||
const isLinchpin = ranges.some(({ endColumn, endRow, startColumn, startRow }: any) => {
|
||||
return startRow <= targetLeftColIndex && targetLeftColIndex <= endRow && startColumn <= targetLeftRowIndex && targetLeftRowIndex <= endColumn;
|
||||
});
|
||||
|
||||
if (isLinchpin) {
|
||||
leftParentCellCustomColName = null;
|
||||
leftParentCellCustomRowName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...custom,
|
||||
leftParentCellCustomColName,
|
||||
leftParentCellCustomRowName,
|
||||
topParentCellCustomColName,
|
||||
topParentCellCustomRowName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译小时
|
||||
* @param hour
|
||||
*/
|
||||
function to12HourFormat(hour: any) {
|
||||
const h = hour % 12 || 12; // 0 => 12, 13 => 1, 14 => 2 ...
|
||||
return h.toString().padStart(2, '0'); // 补零,例如 2 => '02'
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译页眉页脚文本
|
||||
*/
|
||||
/**
|
||||
* 翻译页眉页脚文本
|
||||
*/
|
||||
export function translateHeaderFooterText(orientation: string, printConfig: any): string {
|
||||
if (!printConfig?.customHeaderFooterValue?.[orientation]) return '';
|
||||
|
||||
let targetValue = printConfig?.customHeaderFooterValue[orientation];
|
||||
|
||||
// 日期和时间的预计算,减少 slice 调用
|
||||
const dateTime = printConfig?.currentDateTime || '';
|
||||
const dateA = dateTime.slice(0, 10); // YYYY-MM-DD
|
||||
const dateB = dateTime.slice(5, 10); // YYYY-MM
|
||||
const dateC = `${dateTime.slice(5, 7)}/${dateTime.slice(8, 10)}/${dateTime.slice(0, 4)}`; // MM/DD/YYYY
|
||||
const dateD = `${dateTime.slice(5, 7)}/${dateTime.slice(8, 10)}`; // MM/DD
|
||||
const timeA = dateTime.slice(11, 19); // HH:mm:ss
|
||||
const timeB = dateTime.slice(11, 16); // HH:mm
|
||||
|
||||
const hourFormat = to12HourFormat(dateTime.slice(11, 13));
|
||||
const hour = Number(dateTime.slice(11, 13));
|
||||
|
||||
const timeC = `${hour < 13 ? 'AM' : 'PM'} ${hourFormat}:${dateTime.slice(14, 19)}`; // HH:mm:ss
|
||||
const timeD = `${hour < 13 ? 'AM' : 'PM'} ${hourFormat}:${dateTime.slice(14, 16)}`; // HH:mm:ss
|
||||
|
||||
// 依次替换占位符,保持原有业务逻辑
|
||||
targetValue = targetValue
|
||||
.replace('@ReportName', printConfig.workbookTitleText)
|
||||
.replace('@SheetName', printConfig.workSheetTitleText)
|
||||
.replace('@TotalPages', printConfig.bookTotalPage)
|
||||
.replace('@PageNumbers', printConfig.bookPageNumber)
|
||||
.replace('@SheetTotalPages', printConfig.sheetTotalPage)
|
||||
.replace('@SheetPageNumbers', printConfig.sheetPageNumber)
|
||||
.replace('@DateA', dateA)
|
||||
.replace('@DateB', dateB)
|
||||
.replace('@DateC', dateC)
|
||||
.replace('@DateD', dateD)
|
||||
.replace('@TimeA', timeA)
|
||||
.replace('@TimeB', timeB)
|
||||
.replace('@TimeC', timeC)
|
||||
.replace('@TimeD', timeD);
|
||||
|
||||
return targetValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打印页面的样式
|
||||
* @param paperType - 纸张类型,对应 `JnpfPrintPaperSizeForType` 中的键值
|
||||
* @param direction - 打印方向,取值为 `JnpfPrintDirectionEnum.portrait`(纵向)或 `JnpfPrintDirectionEnum.landscape`(横向)
|
||||
* @returns 一个包含打印样式的 `<style>` 元素
|
||||
*/
|
||||
export function getPrintPageStyle(paperType: string, direction: string) {
|
||||
const { h, w } = JnpfPrintPaperSizeForType[paperType as keyof typeof JnpfPrintPaperSizeForType] ?? {};
|
||||
const width = direction === JnpfPrintDirectionEnum?.portrait ? w : h;
|
||||
const height = direction === JnpfPrintDirectionEnum?.portrait ? h : w;
|
||||
const style = `
|
||||
@page {
|
||||
size: ${width}px ${height}px;
|
||||
}
|
||||
@page {
|
||||
margin: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
@media print {
|
||||
body > * {
|
||||
display: none!important;
|
||||
}
|
||||
#jnpfReportPrint, #jnpfReportPrint * {
|
||||
display: block!important;
|
||||
height: fit-content;
|
||||
overflow: visible;
|
||||
top: 0;
|
||||
width: fit-content;
|
||||
}
|
||||
#jnpfReportPrint .printContainer {
|
||||
page-break-after: always!important;
|
||||
height: ${height}px;
|
||||
width: ${width}px;
|
||||
position: relative;
|
||||
}
|
||||
}`;
|
||||
const $style = document.createElement('style');
|
||||
$style.innerHTML = style;
|
||||
$style.className = 'jnpfPrintCss';
|
||||
return $style;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Univer 0.8.3 内置 UI 语言包聚合(简中 / 英文)
|
||||
* 繁中无官方包:沿用 ZH_CN 内置 + zq 自定义 zh-TW 菜单文案
|
||||
*/
|
||||
import { LocaleType, merge } from '@univerjs/core';
|
||||
import DesignEnUS from '@univerjs/design/locale/en-US';
|
||||
import DesignZhCN from '@univerjs/design/locale/zh-CN';
|
||||
import DocsUIEnUS from '@univerjs/docs-ui/locale/en-US';
|
||||
import DocsUIZhCN from '@univerjs/docs-ui/locale/zh-CN';
|
||||
import DrawingUIEnUS from '@univerjs/drawing-ui/locale/en-US';
|
||||
import DrawingUIZhCN from '@univerjs/drawing-ui/locale/zh-CN';
|
||||
import FindReplaceEnUS from '@univerjs/find-replace/locale/en-US';
|
||||
import FindReplaceZhCN from '@univerjs/find-replace/locale/zh-CN';
|
||||
import SheetsConditionalFormattingUIEnUS from '@univerjs/sheets-conditional-formatting-ui/locale/en-US';
|
||||
import SheetsConditionalFormattingUIZhCN from '@univerjs/sheets-conditional-formatting-ui/locale/zh-CN';
|
||||
import SheetsCrosshairHighlightEnUS from '@univerjs/sheets-crosshair-highlight/locale/en-US';
|
||||
import SheetsCrosshairHighlightZhCN from '@univerjs/sheets-crosshair-highlight/locale/zh-CN';
|
||||
import SheetsDataValidationUIEnUS from '@univerjs/sheets-data-validation-ui/locale/en-US';
|
||||
import SheetsDataValidationZhCN from '@univerjs/sheets-data-validation-ui/locale/zh-CN';
|
||||
import SheetsDrawingUIEnUS from '@univerjs/sheets-drawing-ui/locale/en-US';
|
||||
import SheetsDrawingUIZhCN from '@univerjs/sheets-drawing-ui/locale/zh-CN';
|
||||
import SheetsFilterUIEnUS from '@univerjs/sheets-filter-ui/locale/en-US';
|
||||
import SheetsFilterUIZhCN from '@univerjs/sheets-filter-ui/locale/zh-CN';
|
||||
import SheetsFindReplaceEnUS from '@univerjs/sheets-find-replace/locale/en-US';
|
||||
import SheetsFindReplaceZhCN from '@univerjs/sheets-find-replace/locale/zh-CN';
|
||||
import SheetsFormulaUIEnUS from '@univerjs/sheets-formula-ui/locale/en-US';
|
||||
import SheetsFormulaUIZhCN from '@univerjs/sheets-formula-ui/locale/zh-CN';
|
||||
import SheetsHyperLinkUIEnUS from '@univerjs/sheets-hyper-link-ui/locale/en-US';
|
||||
import SheetsHyperLinkUIZhCN from '@univerjs/sheets-hyper-link-ui/locale/zh-CN';
|
||||
import SheetsNumfmtUIEnUS from '@univerjs/sheets-numfmt-ui/locale/en-US';
|
||||
import SheetsNumfmtUIZhCN from '@univerjs/sheets-numfmt-ui/locale/zh-CN';
|
||||
import SheetsSortUIEnUS from '@univerjs/sheets-sort-ui/locale/en-US';
|
||||
import SheetsSortUIZhCN from '@univerjs/sheets-sort-ui/locale/zh-CN';
|
||||
import SheetsThreadCommentUIEnUS from '@univerjs/sheets-thread-comment-ui/locale/en-US';
|
||||
import SheetsThreadCommentUIZhCN from '@univerjs/sheets-thread-comment-ui/locale/zh-CN';
|
||||
import SheetsUIEnUS from '@univerjs/sheets-ui/locale/en-US';
|
||||
import SheetsUIZhCN from '@univerjs/sheets-ui/locale/zh-CN';
|
||||
import SheetsZenEditorEnUS from '@univerjs/sheets-zen-editor/locale/en-US';
|
||||
import SheetsZenEditorZhCN from '@univerjs/sheets-zen-editor/locale/zh-CN';
|
||||
import SheetsEnUS from '@univerjs/sheets/locale/en-US';
|
||||
import SheetsZhCN from '@univerjs/sheets/locale/zh-CN';
|
||||
import ThreadCommentUIEnUS from '@univerjs/thread-comment-ui/locale/en-US';
|
||||
import ThreadCommentUIZhCN from '@univerjs/thread-comment-ui/locale/zh-CN';
|
||||
import UIEnUS from '@univerjs/ui/locale/en-US';
|
||||
import UIZhCN from '@univerjs/ui/locale/zh-CN';
|
||||
|
||||
import zqEnUS from '../locales/en-US';
|
||||
import zqZhCN from '../locales/zh-CN';
|
||||
import zqZhTW from '../locales/zh-TW';
|
||||
|
||||
function mergeZhCNBuiltin() {
|
||||
return merge(
|
||||
{},
|
||||
DesignZhCN,
|
||||
UIZhCN,
|
||||
DocsUIZhCN,
|
||||
SheetsZhCN,
|
||||
SheetsUIZhCN,
|
||||
SheetsNumfmtUIZhCN,
|
||||
SheetsFormulaUIZhCN,
|
||||
SheetsFilterUIZhCN,
|
||||
SheetsSortUIZhCN,
|
||||
SheetsDataValidationZhCN,
|
||||
SheetsConditionalFormattingUIZhCN,
|
||||
SheetsHyperLinkUIZhCN,
|
||||
DrawingUIZhCN,
|
||||
SheetsDrawingUIZhCN,
|
||||
ThreadCommentUIZhCN,
|
||||
SheetsThreadCommentUIZhCN,
|
||||
FindReplaceZhCN,
|
||||
SheetsFindReplaceZhCN,
|
||||
SheetsCrosshairHighlightZhCN,
|
||||
SheetsZenEditorZhCN,
|
||||
);
|
||||
}
|
||||
|
||||
function mergeEnUSBuiltin() {
|
||||
return merge(
|
||||
{},
|
||||
DesignEnUS,
|
||||
UIEnUS,
|
||||
DocsUIEnUS,
|
||||
SheetsEnUS,
|
||||
SheetsUIEnUS,
|
||||
SheetsNumfmtUIEnUS,
|
||||
SheetsFormulaUIEnUS,
|
||||
SheetsFilterUIEnUS,
|
||||
SheetsSortUIEnUS,
|
||||
SheetsDataValidationUIEnUS,
|
||||
SheetsConditionalFormattingUIEnUS,
|
||||
SheetsHyperLinkUIEnUS,
|
||||
DrawingUIEnUS,
|
||||
SheetsDrawingUIEnUS,
|
||||
ThreadCommentUIEnUS,
|
||||
SheetsThreadCommentUIEnUS,
|
||||
FindReplaceEnUS,
|
||||
SheetsFindReplaceEnUS,
|
||||
SheetsCrosshairHighlightEnUS,
|
||||
SheetsZenEditorEnUS,
|
||||
);
|
||||
}
|
||||
|
||||
/** 根据应用语言解析 Univer 实例 locale 与 locales 配置 */
|
||||
export function resolveUniverLocaleConfig(appLocale?: string): {
|
||||
locale: LocaleType;
|
||||
locales: Partial<Record<LocaleType, object>>;
|
||||
} {
|
||||
const zhBuiltin = mergeZhCNBuiltin();
|
||||
const enBuiltin = mergeEnUSBuiltin();
|
||||
|
||||
if (appLocale === 'en-US') {
|
||||
return {
|
||||
locale: LocaleType.EN_US,
|
||||
locales: {
|
||||
[LocaleType.EN_US]: merge({}, enBuiltin, zqEnUS),
|
||||
[LocaleType.ZH_CN]: merge({}, zhBuiltin, zqZhCN),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const zqOverlay = appLocale === 'zh-TW' ? zqZhTW : zqZhCN;
|
||||
return {
|
||||
locale: LocaleType.ZH_CN,
|
||||
locales: {
|
||||
[LocaleType.ZH_CN]: merge({}, zhBuiltin, zqOverlay),
|
||||
[LocaleType.EN_US]: merge({}, enBuiltin, zqEnUS),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
import type { IWorkbookData } from '@univerjs/core';
|
||||
import { IImageIoService, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
// 数据验证
|
||||
import { UniverDataValidationPlugin } from '@univerjs/data-validation';
|
||||
import { defaultTheme } from '@univerjs/design';
|
||||
import { UniverDocsPlugin } from '@univerjs/docs';
|
||||
// 图片
|
||||
import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing';
|
||||
import { UniverDocsUIPlugin } from '@univerjs/docs-ui';
|
||||
import { UniverDrawingPlugin } from '@univerjs/drawing';
|
||||
import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui';
|
||||
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
|
||||
import { UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
// 查找 & 替换
|
||||
import { UniverFindReplacePlugin } from '@univerjs/find-replace';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
// 条件格式
|
||||
import { UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting';
|
||||
import { UniverSheetsConditionalFormattingUIPlugin } from '@univerjs/sheets-conditional-formatting-ui';
|
||||
// 十字高亮
|
||||
import { UniverSheetsCrosshairHighlightPlugin } from '@univerjs/sheets-crosshair-highlight';
|
||||
import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation';
|
||||
import { UniverSheetsDataValidationUIPlugin } from '@univerjs/sheets-data-validation-ui';
|
||||
import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing';
|
||||
import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui';
|
||||
// 筛选
|
||||
import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter';
|
||||
import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui';
|
||||
import { UniverSheetsFindReplacePlugin } from '@univerjs/sheets-find-replace';
|
||||
import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula';
|
||||
import { UniverSheetsFormulaUIPlugin } from '@univerjs/sheets-formula-ui';
|
||||
// 超链接
|
||||
import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link';
|
||||
import { UniverSheetsHyperLinkUIPlugin } from '@univerjs/sheets-hyper-link-ui';
|
||||
import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt';
|
||||
import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui';
|
||||
// 排序
|
||||
import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
|
||||
import { UniverSheetsSortUIPlugin } from '@univerjs/sheets-sort-ui';
|
||||
import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment';
|
||||
// import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
// 禅编译器
|
||||
import { UniverSheetsZenEditorPlugin } from '@univerjs/sheets-zen-editor';
|
||||
// 评论/批注
|
||||
import { UniverThreadCommentPlugin } from '@univerjs/thread-comment';
|
||||
import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui';
|
||||
import { UniverUIPlugin } from '@univerjs/ui';
|
||||
// uniscript
|
||||
// import { UniverUniscriptPlugin } from '@univerjs/uniscript';
|
||||
// 水印
|
||||
import { UniverWatermarkPlugin } from '@univerjs/watermark';
|
||||
// 打印
|
||||
// import { UniverSheetsPrintPlugin } from '@univerjs-pro/sheets-print';
|
||||
|
||||
import { ZqSheetsCellImagePlugin } from '../plugins/sheet-cell-image.plugin';
|
||||
// 自定义
|
||||
import { ZqSheetsCellPlugin } from '../plugins/sheet-cell.plugin';
|
||||
import { ZqSheetsDialogPlugin } from '../plugins/sheet-dialog.plugin';
|
||||
import { ZqSheetsExcelFilePlugin } from '../plugins/sheet-excel-file.plugin';
|
||||
import { ZqSheetsFloatDomPlugin } from '../plugins/sheet-float-dom.plugin';
|
||||
import { ZqSheetsFloatEchartPlugin } from '../plugins/sheet-float-echart.plugin';
|
||||
import { ZqSheetsFloatImagePlugin } from '../plugins/sheet-float-image.plugin';
|
||||
import { ZqSheetsPreviewPlugin } from '../plugins/sheet-preview.plugin';
|
||||
import { ZqSheetsPrintPlugin } from '../plugins/sheet-print.plugin';
|
||||
import { ZqSheetsRangePlugin } from '../plugins/sheet-range.plugin';
|
||||
import { DefaultFloatEchartHeight, DefaultFloatEchartWidth, ZqUniverFloatEchartKey, ZqUniverFloatImageKey } from './define';
|
||||
import { hydrateExpressionCellsInSnapshot } from './expression-cell';
|
||||
import { correctSheetCellData, isEmptyObject, isNullOrUndefined, moveArrayValueToEnd } from './index';
|
||||
import { resolveUniverLocaleConfig } from './locale-packs';
|
||||
|
||||
// 创建univer实例
|
||||
export function createUniverInstance(config: any) {
|
||||
const { container, contextMenu, footer, header, workbookData, darkMode, appLocale } = config ?? {};
|
||||
const { locale, locales } = resolveUniverLocaleConfig(appLocale);
|
||||
|
||||
const univerInstance = new Univer({
|
||||
locale,
|
||||
locales,
|
||||
theme: defaultTheme,
|
||||
darkMode,
|
||||
});
|
||||
|
||||
univerInstance.registerPlugin(UniverRenderEnginePlugin);
|
||||
univerInstance.registerPlugin(UniverFormulaEnginePlugin);
|
||||
univerInstance.registerPlugin(UniverUIPlugin, {
|
||||
container,
|
||||
contextMenu,
|
||||
footer,
|
||||
header,
|
||||
});
|
||||
univerInstance.registerPlugin(UniverDocsPlugin);
|
||||
univerInstance.registerPlugin(UniverDocsUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsUIPlugin, {
|
||||
menu: {
|
||||
'sheet.command.delete-range-move-left-confirm': {
|
||||
hidden: true,
|
||||
},
|
||||
'sheet.command.delete-range-move-up-confirm': {
|
||||
hidden: true,
|
||||
},
|
||||
'sheet.command.add-range-protection-from-toolbar': {
|
||||
hidden: true,
|
||||
},
|
||||
'sheet.command.add-range-protection-from-context-menu': {
|
||||
hidden: true,
|
||||
},
|
||||
'sheet.contextMenu.permission': {
|
||||
hidden: true,
|
||||
}
|
||||
},
|
||||
});
|
||||
// univerInstance.registerPlugin(UniverSheetsUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsFormulaPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsFormulaUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsNumfmtPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsNumfmtUIPlugin);
|
||||
|
||||
// 这个和注销的冲突掉了
|
||||
univerInstance.registerPlugin(UniverSheetsHyperLinkPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsHyperLinkUIPlugin);
|
||||
|
||||
univerInstance.registerPlugin(UniverDocsDrawingPlugin);
|
||||
univerInstance.registerPlugin(UniverDrawingPlugin, {
|
||||
override: [[IImageIoService, null]],
|
||||
});
|
||||
univerInstance.registerPlugin(UniverDrawingUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsDrawingPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsDrawingUIPlugin);
|
||||
|
||||
univerInstance.registerPlugin(UniverFindReplacePlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsFindReplacePlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsFilterPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsFilterUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsSortPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsSortUIPlugin);
|
||||
univerInstance.registerPlugin(UniverDataValidationPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsDataValidationPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsDataValidationUIPlugin, {
|
||||
showEditOnDropdown: true,
|
||||
});
|
||||
univerInstance.registerPlugin(UniverSheetsConditionalFormattingPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsConditionalFormattingUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsCrosshairHighlightPlugin);
|
||||
univerInstance.registerPlugin(UniverThreadCommentPlugin);
|
||||
univerInstance.registerPlugin(UniverThreadCommentUIPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsThreadCommentPlugin);
|
||||
// univerInstance.registerPlugin(UniverSheetsThreadCommentUIPlugin);
|
||||
// univerInstance.registerPlugin(UniverUniscriptPlugin);
|
||||
univerInstance.registerPlugin(UniverSheetsZenEditorPlugin);
|
||||
univerInstance.registerPlugin(UniverWatermarkPlugin);
|
||||
|
||||
// univerInstance.registerPlugin(UniverSheetsPrintPlugin);
|
||||
|
||||
univerInstance.registerPlugin(ZqSheetsCellPlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsRangePlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsDialogPlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsExcelFilePlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsFloatDomPlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsFloatEchartPlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsCellImagePlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsFloatImagePlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsPreviewPlugin);
|
||||
univerInstance.registerPlugin(ZqSheetsPrintPlugin);
|
||||
|
||||
univerInstance.createUnit(UniverInstanceType.UNIVER_SHEET, workbookData);
|
||||
return univerInstance;
|
||||
}
|
||||
|
||||
// 解析创建univer实例的快照数据
|
||||
export function analysisCreateUnitData(workbookData: IWorkbookData | string, createMode: string, isReadonly: boolean) {
|
||||
try {
|
||||
const parseWorkbookData = typeof workbookData === 'string' ? JSON.parse(workbookData) : workbookData;
|
||||
|
||||
if (createMode === 'design' && !isReadonly) {
|
||||
// 设计模式:补齐表达式单元格 f/t,便于公式栏展示
|
||||
return hydrateExpressionCellsInSnapshot(
|
||||
JSON.parse(JSON.stringify(parseWorkbookData)),
|
||||
);
|
||||
}
|
||||
|
||||
const { resources = [] } = parseWorkbookData ?? {};
|
||||
|
||||
// 主要是预览的情况下不让图表可操作
|
||||
const updatedResources = resources?.map((resource: any) => {
|
||||
if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
|
||||
const parseResourceData = JSON.parse(resource?.data ?? '{}');
|
||||
|
||||
const updatedParseResourceData = Object.fromEntries(
|
||||
Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
|
||||
const sheetResourceOptionData = sheetResourceOption?.data || {};
|
||||
|
||||
const updateSheetResourceOption = Object.fromEntries(
|
||||
Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
|
||||
const { componentKey, ...floatDomValueRest } = floatDomValue || {};
|
||||
|
||||
return componentKey === ZqUniverFloatEchartKey || componentKey === ZqUniverFloatImageKey
|
||||
? [
|
||||
floatDomKey,
|
||||
{
|
||||
...floatDomValueRest,
|
||||
allowTransform: false,
|
||||
componentKey,
|
||||
},
|
||||
]
|
||||
: [floatDomKey, floatDomValue];
|
||||
}),
|
||||
);
|
||||
|
||||
return [sheetKey, { ...sheetResourceOption, data: updateSheetResourceOption }];
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...resource,
|
||||
data: JSON.stringify(updatedParseResourceData),
|
||||
};
|
||||
} else {
|
||||
return resource;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...parseWorkbookData,
|
||||
resources: updatedResources,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 整理设计的数据
|
||||
export function arrangeDesignWorkbookData(snapshot: any, floatEchartDataCaches: any, floatImageDataCaches: any) {
|
||||
const updateFloatEcharts: Record<string, any> = {};
|
||||
const updateCellEcharts: Record<string, any> = {};
|
||||
const updateFloatImages: Record<string, any> = {};
|
||||
|
||||
// 给悬浮图表和悬浮图片增加float dom的id 并且剔除已被删除的悬浮图表和悬浮图片数据
|
||||
const hydratedSnapshot = hydrateExpressionCellsInSnapshot(
|
||||
JSON.parse(JSON.stringify(snapshot ?? {})),
|
||||
);
|
||||
const { resources = [], sheets = {} } = hydratedSnapshot ?? {};
|
||||
|
||||
const updatedResources = resources?.map((resource: any) => {
|
||||
if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
|
||||
const parseResourceData = JSON.parse(resource?.data ?? '{}');
|
||||
|
||||
const updatedParseResourceData = Object.fromEntries(
|
||||
Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
|
||||
const sheetResourceOptionData = sheetResourceOption?.data || {};
|
||||
|
||||
const updateSheetResourceOption = Object.fromEntries(
|
||||
Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
|
||||
const { componentKey, drawingId, data = {}, ...floatDomValueRest } = floatDomValue || {};
|
||||
|
||||
if (componentKey === ZqUniverFloatEchartKey) {
|
||||
if (floatEchartDataCaches?.hasOwnProperty(drawingId)) {
|
||||
const { height = DefaultFloatEchartHeight, width = DefaultFloatEchartWidth } = floatDomValue.transform ?? {};
|
||||
|
||||
updateFloatEcharts[drawingId] = {
|
||||
...floatEchartDataCaches[drawingId],
|
||||
height: Math.floor(height),
|
||||
width: Math.floor(width),
|
||||
}; // store中的悬浮图表数据过滤
|
||||
}
|
||||
|
||||
// const domId = floatEchartDataCaches?.[drawingId]?.domId;
|
||||
// const updatedProps = {
|
||||
// ...props,
|
||||
// id: domId,
|
||||
// };
|
||||
|
||||
return [
|
||||
floatDomKey,
|
||||
{
|
||||
...floatDomValueRest,
|
||||
componentKey,
|
||||
drawingId,
|
||||
data,
|
||||
},
|
||||
];
|
||||
} else if (componentKey === ZqUniverFloatImageKey) {
|
||||
if (floatImageDataCaches?.hasOwnProperty(drawingId)) {
|
||||
updateFloatImages[drawingId] = { ...floatImageDataCaches[drawingId] }; // store中的悬浮图片数据过滤
|
||||
}
|
||||
|
||||
// const domId = floatImageDataCaches?.[drawingId]?.domId;
|
||||
// const updatedProps = {
|
||||
// ...data,
|
||||
// id: domId,
|
||||
// };
|
||||
|
||||
return [
|
||||
floatDomKey,
|
||||
{
|
||||
...floatDomValueRest,
|
||||
componentKey,
|
||||
drawingId,
|
||||
data,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [floatDomKey, floatDomValue];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return [sheetKey, { ...sheetResourceOption, data: updateSheetResourceOption }];
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...resource,
|
||||
data: JSON.stringify(updatedParseResourceData),
|
||||
};
|
||||
} else {
|
||||
return resource;
|
||||
}
|
||||
});
|
||||
|
||||
// 取出所有自定义的custom并整理单元格图表的数据
|
||||
const customs: any[] = [];
|
||||
for (const sheetKey in sheets) {
|
||||
const sheet = sheets[sheetKey];
|
||||
const sheetCellData = sheet?.cellData;
|
||||
if (!sheetCellData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rowKey in sheetCellData) {
|
||||
const row = sheetCellData[rowKey];
|
||||
|
||||
for (const colKey in row) {
|
||||
const cellData = row[colKey] ?? {};
|
||||
const custom = cellData?.custom;
|
||||
|
||||
if (custom && !isEmptyObject(custom)) {
|
||||
if (custom.type === 'chart') {
|
||||
if (!isNullOrUndefined(custom)) {
|
||||
const { chartType, color, drawingId, height, type, width, ...restOption } = custom ?? {};
|
||||
updateCellEcharts[drawingId] = {
|
||||
col: colKey,
|
||||
drawingId,
|
||||
echartType: chartType,
|
||||
height,
|
||||
option: restOption,
|
||||
row: rowKey,
|
||||
sheet: sheetKey,
|
||||
width,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (custom.type !== 'jsbarcode' && custom.type !== 'qrCode') {
|
||||
customs.push({
|
||||
cellData,
|
||||
col: colKey,
|
||||
row: rowKey,
|
||||
sheetId: sheetKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cellEcharts: updateCellEcharts,
|
||||
customs,
|
||||
floatEcharts: updateFloatEcharts,
|
||||
floatImages: updateFloatImages,
|
||||
snapshot: {
|
||||
...hydratedSnapshot,
|
||||
resources: updatedResources,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 整理预览的数据
|
||||
export function arrangePreviewWorkbookData(designWorkbookData: any, floatEchartToUniverImgDataCaches: any, floatImageToUniverImgDataCaches: any) {
|
||||
const { customs, floatEcharts, floatImages, snapshot } = designWorkbookData ?? {};
|
||||
|
||||
const { resources = [], sheets } = snapshot ?? {};
|
||||
const updateSheets = { ...sheets };
|
||||
|
||||
Object.entries(sheets)?.forEach(([sheetKey, sheetInfo]: any) => {
|
||||
const { cellData, columnCount, rowCount } = sheetInfo ?? {};
|
||||
|
||||
const resource = resources.find((resourceInfo: any) => resourceInfo?.name === 'SHEET_DRAWING_PLUGIN');
|
||||
const parseResourceData = JSON.parse(resource?.data ?? '{}');
|
||||
|
||||
const hasFloatDom = !isEmptyObject(parseResourceData) && parseResourceData[sheetKey]?.order?.length;
|
||||
|
||||
const updateCellData = hasFloatDom ? correctSheetCellData(cellData, rowCount, columnCount, true) : correctSheetCellData(cellData, rowCount, columnCount);
|
||||
|
||||
updateSheets[sheetKey] = {
|
||||
...sheets[sheetKey],
|
||||
cellData: updateCellData,
|
||||
};
|
||||
});
|
||||
|
||||
const updatedResources = resources?.map((resource: any) => {
|
||||
if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
|
||||
const parseResourceData = JSON.parse(resource?.data ?? '{}');
|
||||
|
||||
const updatedParseResourceData = Object.fromEntries(
|
||||
Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
|
||||
const { data: sheetResourceOptionData = {}, order: originalOrder = [] } = sheetResourceOption || {};
|
||||
|
||||
let sheetOrder = [...originalOrder]; // 使用一个可变的引用来追踪顺序更新
|
||||
|
||||
const updatedSheetData = Object.fromEntries(
|
||||
Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
|
||||
const { componentKey, drawingId, data, sheetTransform, subUnitId, transform, unitId } = floatDomValue || {};
|
||||
|
||||
if (componentKey === ZqUniverFloatEchartKey) {
|
||||
const { id: domId } = data ?? {};
|
||||
|
||||
sheetOrder = moveArrayValueToEnd(sheetOrder, drawingId);
|
||||
|
||||
const source = floatEchartToUniverImgDataCaches?.[domId] ?? '';
|
||||
|
||||
return [
|
||||
floatDomKey,
|
||||
{
|
||||
drawingId,
|
||||
drawingType: 0,
|
||||
imageSourceType: 'BASE64',
|
||||
sheetTransform,
|
||||
source,
|
||||
subUnitId,
|
||||
transform,
|
||||
unitId,
|
||||
data,
|
||||
},
|
||||
];
|
||||
} else if (componentKey === ZqUniverFloatImageKey) {
|
||||
const { id: domId } = data ?? {};
|
||||
|
||||
sheetOrder = moveArrayValueToEnd(sheetOrder, drawingId);
|
||||
|
||||
const source = floatImageToUniverImgDataCaches?.[domId] ?? '';
|
||||
|
||||
return [
|
||||
floatDomKey,
|
||||
{
|
||||
drawingId,
|
||||
drawingType: 0,
|
||||
imageSourceType: source?.startsWith('data:image/') ? 'BASE64' : 'URL',
|
||||
sheetTransform,
|
||||
source,
|
||||
subUnitId,
|
||||
transform,
|
||||
unitId,
|
||||
data,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [floatDomKey, floatDomValue];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return [sheetKey, { ...sheetResourceOption, data: updatedSheetData, order: sheetOrder }];
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...resource,
|
||||
data: JSON.stringify(updatedParseResourceData),
|
||||
};
|
||||
} else {
|
||||
return resource;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
customs,
|
||||
floatEcharts,
|
||||
floatImages,
|
||||
snapshot: {
|
||||
...snapshot,
|
||||
resources: updatedResources,
|
||||
sheets: updateSheets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 判断当前工作本是不是含有自定义的 float dom
|
||||
export function judgeSheetHasCustomFloatDom(data: any): boolean {
|
||||
return Object.values(data).some(
|
||||
(floatDomValue: any) => floatDomValue?.componentKey === ZqUniverFloatEchartKey || floatDomValue?.componentKey === ZqUniverFloatImageKey,
|
||||
);
|
||||
}
|
||||
|
||||
// 监听插入悬浮图表
|
||||
export function onInsertedFloatEchart(zqUniverStore: any, command: any) {
|
||||
const { drawingId } = command?.params ?? {};
|
||||
if (!drawingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const floatEchartDataCaches = zqUniverStore?.floatEchartDataCaches ?? {};
|
||||
|
||||
const updateItems = {
|
||||
...floatEchartDataCaches,
|
||||
[drawingId]: command?.params,
|
||||
};
|
||||
|
||||
zqUniverStore?.setFloatEchartDataCaches(updateItems);
|
||||
}
|
||||
|
||||
// 监听插入悬浮图片
|
||||
export function onInsertedFloatImage(zqUniverStore: any, command: any) {
|
||||
const { drawingId } = command?.params ?? {};
|
||||
if (!drawingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const floatImageDataCaches = zqUniverStore?.floatImageDataCaches ?? {};
|
||||
|
||||
const updateItems = {
|
||||
...floatImageDataCaches,
|
||||
[drawingId]: command?.params,
|
||||
};
|
||||
|
||||
zqUniverStore?.setFloatImageDataCaches(updateItems);
|
||||
}
|
||||
|
||||
// 更新浮动元素的storeId
|
||||
export function updateFloatStoreId(snapshot: any, dynamicZqUniverStoreId: string) {
|
||||
const { resources, ...rest } = snapshot ?? {};
|
||||
if (!resources) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
const updatedResources = resources?.map((resource: any) => {
|
||||
if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
|
||||
const parseResourceData = JSON.parse(resource?.data ?? '{}');
|
||||
|
||||
const updatedParseResourceData = Object.fromEntries(
|
||||
Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
|
||||
const { data: sheetResourceOptionData = {}, order: originalOrder = [] } = sheetResourceOption || {};
|
||||
|
||||
const sheetOrder = [...originalOrder]; // 使用一个可变的引用来追踪顺序更新
|
||||
|
||||
const updatedSheetData = Object.fromEntries(
|
||||
Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
|
||||
const { componentKey, data } = floatDomValue || {};
|
||||
|
||||
if (componentKey === ZqUniverFloatEchartKey || componentKey === ZqUniverFloatImageKey) {
|
||||
const newData = {
|
||||
...data,
|
||||
piniaStoreId: dynamicZqUniverStoreId,
|
||||
};
|
||||
|
||||
return [
|
||||
floatDomKey,
|
||||
{
|
||||
...floatDomValue,
|
||||
data: newData,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [floatDomKey, floatDomValue];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return [sheetKey, { ...sheetResourceOption, data: updatedSheetData, order: sheetOrder }];
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...resource,
|
||||
data: JSON.stringify(updatedParseResourceData),
|
||||
};
|
||||
} else {
|
||||
return resource;
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
...rest,
|
||||
resources: updatedResources,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
const hexList: string[] = [];
|
||||
for (let i = 0; i <= 15; i++) {
|
||||
hexList[i] = i.toString(16);
|
||||
}
|
||||
|
||||
export function buildUUID(): string {
|
||||
let uuid = '';
|
||||
for (let i = 1; i <= 36; i++) {
|
||||
switch (i) {
|
||||
case 9:
|
||||
case 14:
|
||||
case 19:
|
||||
case 24: {
|
||||
uuid += '-';
|
||||
|
||||
break;
|
||||
}
|
||||
case 15: {
|
||||
uuid += 4;
|
||||
|
||||
break;
|
||||
}
|
||||
case 20: {
|
||||
uuid += hexList[(Math.random() * 4) | 8];
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uuid += hexList[(Math.random() * 16) | 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuid.replaceAll('-', '');
|
||||
}
|
||||
export function buildBitUUID(length = 6): string {
|
||||
return buildUUID().slice(0, Math.max(0, length));
|
||||
}
|
||||
|
||||
let unique = 0;
|
||||
export function buildShortUUID(prefix = ''): string {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1_000_000_000);
|
||||
unique++;
|
||||
return `${prefix}_${random}${unique}${String(time)}`;
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, reactive, ref, toRefs, unref } from 'vue';
|
||||
|
||||
import { Button as AButton, Modal as AModal } from 'ant-design-vue';
|
||||
|
||||
import type { CreateUnitProps } from '../Design/index';
|
||||
import ZqUniverDesign from '../Design/index.vue';
|
||||
import { useZqUniverStore } from '../Design/store';
|
||||
import ZqUniverCellEchart from '../Design/univer/components/Echart/cell.vue';
|
||||
import ZqUniverFloatEchartVM from '../Design/univer/components/Echart/floatVM.vue';
|
||||
|
||||
const emits = defineEmits(['print']);
|
||||
|
||||
const zqUniverDesignPreviewRef = ref();
|
||||
const zqUniverFloatEchartVMRef = ref();
|
||||
const zqUniverCellEchartRef = ref();
|
||||
|
||||
const state = reactive<{
|
||||
echartsImageCache: any;
|
||||
internalOpen: boolean;
|
||||
|
||||
zqUniverAPI: any;
|
||||
|
||||
zqUniverStore: any;
|
||||
}>({
|
||||
echartsImageCache: {},
|
||||
internalOpen: false,
|
||||
|
||||
zqUniverAPI: null,
|
||||
|
||||
zqUniverStore: null,
|
||||
});
|
||||
const { echartsImageCache, internalOpen, zqUniverStore } = toRefs(state);
|
||||
|
||||
// 创建预览实例
|
||||
function handleCreatePreviewUnit(options?: CreateUnitProps) {
|
||||
const { cellEcharts: cellEchartsObject, defaultActiveSheetId, floatEcharts, floatImages, snapshot } = options ?? {};
|
||||
if (snapshot === undefined) {
|
||||
console.warn('预览数据错误');
|
||||
return;
|
||||
}
|
||||
|
||||
state.internalOpen = true;
|
||||
|
||||
const updateSheets = JSON.parse(JSON.stringify(snapshot?.sheets ?? {}));
|
||||
|
||||
nextTick(() => {
|
||||
// 单元格图表数据转成成数组
|
||||
const cellEchartsArray = cellEchartsObjectToArray(cellEchartsObject);
|
||||
|
||||
// 单元格图表转单元格图片
|
||||
cellEchartsArray.forEach(cellEchartOption => {
|
||||
const { colKey, drawingId, height: echartHeight, option: echartOption, rowKey, sheetKey, width: echartWidth } = cellEchartOption;
|
||||
|
||||
const echartImg = unref(zqUniverCellEchartRef).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 = JSON.parse(JSON.stringify(updateSheets));
|
||||
|
||||
// 构建univer实例
|
||||
const { zqUniverAPI } = unref(zqUniverDesignPreviewRef)?.handleCreateDesignUnit({
|
||||
defaultActiveSheetId,
|
||||
floatEcharts,
|
||||
floatImages,
|
||||
loading: true,
|
||||
mode: 'preview',
|
||||
snapshot,
|
||||
uiContextMenu: false,
|
||||
uiHeader: false,
|
||||
workbookReadonly: true,
|
||||
});
|
||||
state.zqUniverAPI = zqUniverAPI ?? null;
|
||||
|
||||
// 创建悬浮图表的图片
|
||||
createFloatEchartsImage(floatEcharts);
|
||||
});
|
||||
}
|
||||
|
||||
// 打印
|
||||
function handlePrint() {
|
||||
const info = getInstanceInfo();
|
||||
|
||||
emits('print', { ...info });
|
||||
}
|
||||
|
||||
// 导出
|
||||
function handleExport() {
|
||||
const info = getInstanceInfo();
|
||||
|
||||
console.log(info?.snapshot);
|
||||
alert('看控制台的快照');
|
||||
}
|
||||
|
||||
// 关闭弹窗 (建议有个时间缓存周期再关闭弹窗,要销毁设计器)
|
||||
function handleCancel() {
|
||||
unref(zqUniverDesignPreviewRef)?.handleDisposeUnit();
|
||||
resetUniverState(); // 重置实例状态
|
||||
}
|
||||
|
||||
// 单元格图表数据转成成数组
|
||||
function cellEchartsObjectToArray(data: any) {
|
||||
const res: any[] = [];
|
||||
for (const sheetKey in data) {
|
||||
const rows = data[sheetKey];
|
||||
|
||||
for (const rowKey in rows) {
|
||||
const cols = rows[rowKey];
|
||||
|
||||
for (const colKey in cols) {
|
||||
const { drawingId, height, option, width } = cols[colKey];
|
||||
|
||||
res.push({
|
||||
colKey,
|
||||
drawingId,
|
||||
height,
|
||||
option,
|
||||
rowKey,
|
||||
sheetKey,
|
||||
width,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// 创建悬浮图表的图片信息
|
||||
function createFloatEchartsImage(echarts: any) {
|
||||
const dynamicZqUniverStoreId = unref(zqUniverDesignPreviewRef)?.getDynamicStoreId();
|
||||
if (!dynamicZqUniverStoreId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zqUniverStore = useZqUniverStore(dynamicZqUniverStoreId);
|
||||
state.zqUniverStore = zqUniverStore; // 先赋值给 state
|
||||
|
||||
const caches = { ...unref(zqUniverStore.floatEchartToUniverImgDataCaches) };
|
||||
|
||||
for (const sheetKey in echarts) {
|
||||
const target = echarts[sheetKey];
|
||||
|
||||
const { domId, height, option, width } = target;
|
||||
|
||||
const res = unref(zqUniverFloatEchartVMRef).render(domId, option, width, height);
|
||||
caches[res.domId] = res?.source ?? {};
|
||||
|
||||
Object.assign(state.echartsImageCache, caches); // 直接修改原对象,保持响应性
|
||||
}
|
||||
}
|
||||
|
||||
// 获取实例数据
|
||||
function getInstanceInfo() {
|
||||
zqUniverStore.value?.setFloatEchartToUniverImgDataCaches(echartsImageCache.value);
|
||||
|
||||
const { snapshot } = unref(zqUniverDesignPreviewRef)?.getPreviewWorkbookData();
|
||||
const defaultActiveSheetId = unref(zqUniverDesignPreviewRef)?.getActiveWorksheetId();
|
||||
|
||||
return {
|
||||
defaultActiveSheetId,
|
||||
snapshot,
|
||||
};
|
||||
}
|
||||
|
||||
// 重置实例状态
|
||||
function resetUniverState() {
|
||||
state.zqUniverAPI = null;
|
||||
state.zqUniverStore = null;
|
||||
|
||||
state.echartsImageCache = {};
|
||||
}
|
||||
|
||||
defineExpose({ handleCreatePreviewUnit });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AModal v-model:open="internalOpen" title="设计预览" width="100%" wrap-class-name="preview-full-modal" @cancel="handleCancel">
|
||||
<!-- 测试按钮,随时可以删除掉 -->
|
||||
<div class="buttons-wrap">
|
||||
<AButton type="primary" @click="handlePrint">打印预览</AButton>
|
||||
<AButton style="margin-left: 10px; background: deeppink" type="primary" @click="handleExport">导出快照</AButton>
|
||||
</div>
|
||||
|
||||
<div class="zq-univer-preview-module">
|
||||
<ZqUniverDesign v-if="internalOpen" ref="zqUniverDesignPreviewRef" />
|
||||
|
||||
<div id="floatEchartVMContent" class="cell-echarts-vm-content">
|
||||
<ZqUniverFloatEchartVM ref="zqUniverFloatEchartVMRef" univer-create-mode="preview" />
|
||||
</div>
|
||||
<div id="cellEchartsContent" class="cell-echarts-content">
|
||||
<ZqUniverCellEchart ref="zqUniverCellEchartRef" univer-create-mode="preview" />
|
||||
</div>
|
||||
</div>
|
||||
</AModal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.preview-full-modal {
|
||||
.ant-modal {
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh);
|
||||
padding: 20px 10px 0;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.zq-univer-preview-module {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 隐藏的dom节点
|
||||
.cell-echarts-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
// 隐藏的dom节点
|
||||
.cell-echarts-vm-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,229 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, reactive, toRefs, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElOption,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import {
|
||||
jnpfPaperPaddingTypeOptions,
|
||||
jnpfPaperTypeOptions,
|
||||
jnpfPrintAreaOptions,
|
||||
jnpfPrintDirectionOptions,
|
||||
jnpfPrintHAlignOptions,
|
||||
jnpfPrintScaleOptions,
|
||||
jnpfPrintVAlignOptions,
|
||||
} from '../../Design/univer/utils/define';
|
||||
|
||||
const props = defineProps(['defaultPrintConfig', 'sheetFreezeStatus']);
|
||||
|
||||
const emits = defineEmits(['change']);
|
||||
|
||||
interface State {
|
||||
formData: Record<string, any>;
|
||||
}
|
||||
const state = reactive<State>({
|
||||
formData: {},
|
||||
});
|
||||
const { formData } = toRefs(state);
|
||||
|
||||
let isInitialized = false;
|
||||
|
||||
function getRadioGroupOptions(data: any[] = []) {
|
||||
return data?.map((item: any) => ({
|
||||
label: item.fullName,
|
||||
value: item.id,
|
||||
}));
|
||||
}
|
||||
|
||||
function labelForOption(item: { fullName: string; id: string }) {
|
||||
const key = `report-manager.printForm.options.${item.id}`;
|
||||
const text = $t(key);
|
||||
return text === key ? item.fullName : text;
|
||||
}
|
||||
|
||||
function labelForAlignOption(
|
||||
item: { fullName: string; id: string },
|
||||
group: 'hAlign' | 'vAlign',
|
||||
) {
|
||||
const key = `report-manager.printForm.options.${group}.${item.id}`;
|
||||
const text = $t(key);
|
||||
return text === key ? item.fullName : text;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.sheetFreezeStatus,
|
||||
() => {
|
||||
const updatePrintConfig = {
|
||||
...props.defaultPrintConfig,
|
||||
xFreeze: props.sheetFreezeStatus?.colHasFreeze,
|
||||
yFreeze: props.sheetFreezeStatus?.rowHasFreeze,
|
||||
};
|
||||
|
||||
state.formData = JSON.parse(JSON.stringify(updatePrintConfig));
|
||||
|
||||
nextTick(() => {
|
||||
isInitialized = true;
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const emitChangeDebounced = debounce((val: any) => {
|
||||
emits('change', val);
|
||||
}, 300);
|
||||
|
||||
watch(
|
||||
() => state.formData,
|
||||
(val) => {
|
||||
if (isInitialized) {
|
||||
emitChangeDebounced(val);
|
||||
}
|
||||
},
|
||||
{ deep: true, flush: 'post' },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="print-render-form-content m-3 rounded-[8px]">
|
||||
<ElForm :model="formData" label-position="top" size="small">
|
||||
<ElFormItem :label="$t('report-manager.printForm.printArea')">
|
||||
<ElSelect v-model="formData.printArea" class="w-full">
|
||||
<ElOption
|
||||
v-for="item in jnpfPrintAreaOptions"
|
||||
:key="item.id"
|
||||
:label="labelForOption(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.paperType')">
|
||||
<ElSelect v-model="formData.paperType" class="w-full">
|
||||
<ElOption
|
||||
v-for="item in jnpfPaperTypeOptions"
|
||||
:key="item.id"
|
||||
:label="labelForOption(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.padding')">
|
||||
<ElSelect v-model="formData.padding" class="w-full">
|
||||
<ElOption
|
||||
v-for="item in jnpfPaperPaddingTypeOptions"
|
||||
:key="item.id"
|
||||
:label="labelForOption(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.direction')">
|
||||
<ElRadioGroup v-model="formData.direction" size="small">
|
||||
<ElRadioButton
|
||||
v-for="opt in getRadioGroupOptions(jnpfPrintDirectionOptions)"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ labelForOption({ fullName: opt.label, id: opt.value }) }}
|
||||
</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.scale')">
|
||||
<ElSelect v-model="formData.printScale" class="w-full">
|
||||
<ElOption
|
||||
v-for="item in jnpfPrintScaleOptions"
|
||||
:key="item.id"
|
||||
:label="labelForOption(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.hAlign')">
|
||||
<ElRadioGroup v-model="formData.hAlign" size="small">
|
||||
<ElRadioButton
|
||||
v-for="opt in getRadioGroupOptions(jnpfPrintHAlignOptions)"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ labelForAlignOption({ fullName: opt.label, id: opt.value }, 'hAlign') }}
|
||||
</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.vAlign')">
|
||||
<ElRadioGroup v-model="formData.vAlign" size="small">
|
||||
<ElRadioButton
|
||||
v-for="opt in getRadioGroupOptions(jnpfPrintVAlignOptions)"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ labelForAlignOption({ fullName: opt.label, id: opt.value }, 'vAlign') }}
|
||||
</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.gridlines')">
|
||||
<ElSwitch v-model="formData.gridlines" />
|
||||
</ElFormItem>
|
||||
<div class="flex gap-2">
|
||||
<ElFormItem :label="$t('report-manager.printForm.workbookTitle')" class="flex-1">
|
||||
<ElSwitch v-model="formData.workbookTitle" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.worksheetTitle')" class="flex-1">
|
||||
<ElSwitch v-model="formData.worksheetTitle" />
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ElFormItem :label="$t('report-manager.printForm.printDate')" class="flex-1">
|
||||
<ElSwitch v-model="formData.printDate" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.printTime')" class="flex-1">
|
||||
<ElSwitch v-model="formData.printTime" />
|
||||
</ElFormItem>
|
||||
</div>
|
||||
<ElFormItem :label="$t('report-manager.printForm.pageNumber')">
|
||||
<ElSwitch v-model="formData.pageNumber" />
|
||||
</ElFormItem>
|
||||
<div class="flex gap-2">
|
||||
<ElFormItem :label="$t('report-manager.printForm.yFreeze')" class="flex-1">
|
||||
<ElSwitch
|
||||
v-model="formData.yFreeze"
|
||||
:disabled="!sheetFreezeStatus?.rowHasFreeze"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('report-manager.printForm.xFreeze')" class="flex-1">
|
||||
<ElSwitch
|
||||
v-model="formData.xFreeze"
|
||||
:disabled="!sheetFreezeStatus?.colHasFreeze"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</div>
|
||||
</ElForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.print-render-form-content {
|
||||
box-sizing: border-box;
|
||||
width: 290px;
|
||||
height: calc(100% - 24px);
|
||||
padding: 16px;
|
||||
overflow: hidden auto;
|
||||
background: var(--el-bg-color, #fff);
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, reactive, ref, toRefs, unref } from 'vue';
|
||||
|
||||
import { ZqSheetsPrintUiService } from '../../../Design/univer/services/sheet-print-ui.service';
|
||||
import { printA4PaperHeight, printA4PaperWidth } from '../../../Design/univer/utils/define';
|
||||
|
||||
const props = defineProps(['zqUniverApi', 'pageLayout', 'printConfig', 'watermarkConfig']);
|
||||
const emits = defineEmits(['pageRendered']);
|
||||
|
||||
// ✅ 页面容器引用
|
||||
const pageContainerRef = ref<HTMLElement | null>(null);
|
||||
const defaultContainerStyle = {
|
||||
height: printA4PaperHeight,
|
||||
width: printA4PaperWidth,
|
||||
};
|
||||
|
||||
const state = reactive<{
|
||||
containerStyle: any;
|
||||
renderInstance: any;
|
||||
}>({
|
||||
containerStyle: defaultContainerStyle,
|
||||
renderInstance: null,
|
||||
});
|
||||
const { containerStyle } = toRefs(state);
|
||||
|
||||
onMounted(() => {
|
||||
const { zqUniverApi, pageLayout = {}, printConfig = {}, watermarkConfig = {} } = props ?? {};
|
||||
|
||||
const accessor = zqUniverApi?.getInjector();
|
||||
if (!accessor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { paperSize = {} } = pageLayout;
|
||||
const containerScale = countContainerScale(paperSize); // 打印预览容器的伸缩比
|
||||
|
||||
state.renderInstance = new ZqSheetsPrintUiService(accessor, pageLayout, printConfig, watermarkConfig, containerScale);
|
||||
if (!state.renderInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { h: paperSizeHeight = printA4PaperHeight, w: paperSizeWidth = printA4PaperWidth } = paperSize;
|
||||
state.containerStyle = {
|
||||
height: `${paperSizeHeight * containerScale}px`,
|
||||
width: `${paperSizeWidth * containerScale}px`,
|
||||
};
|
||||
|
||||
state.renderInstance.container.style.cssText = 'width: 100%; height: 100%;';
|
||||
|
||||
nextTick(() => {
|
||||
unref(pageContainerRef)?.append(state.renderInstance.container);
|
||||
state.renderInstance.container.append(state.renderInstance.root);
|
||||
state.renderInstance?.setDirty(true); // 设置脏状态(用于调整视口大小和滚动值的状态)
|
||||
state.renderInstance?.renderPage(); // 渲染页面
|
||||
state.renderInstance?.renderOnReady(); // 主要是在页面变换时确保页面内容的及时更新,并且做了订阅和销毁的管理。
|
||||
emits('pageRendered');
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(async () => {
|
||||
await state.renderInstance?.dispose();
|
||||
|
||||
state.renderInstance = null;
|
||||
state.containerStyle = defaultContainerStyle;
|
||||
});
|
||||
|
||||
// 计算打印预览容器的伸缩比
|
||||
function countContainerScale(paperSize: any) {
|
||||
const { w = printA4PaperWidth } = paperSize;
|
||||
|
||||
return Math.round((printA4PaperWidth / w) * 10) / 10;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="pageContainerRef" :style="containerStyle" class="print-page-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.print-page-container {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
margin: 28px auto;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 4px 24px 0 rgb(30 34 43 / 8%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,584 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, reactive, ref, toRefs } from 'vue';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { ZqSheetsPrintUiService } from '../../Design/univer/services/sheet-print-ui.service';
|
||||
import { getPrintPageStyle } from '../../Design/univer/utils';
|
||||
import { printA4PaperHeight, printA4PaperWidth } from '../../Design/univer/utils/define';
|
||||
import ZqUniverPrintRenderPage from './Page/index.vue';
|
||||
|
||||
defineOptions({ name: 'ZqUniverPrintRender' });
|
||||
|
||||
const props = defineProps(['zqUniverApi']);
|
||||
const emits = defineEmits(['previewRenderProgress', 'printRenderProgress', 'browserPrintShow']);
|
||||
|
||||
const batchRenderSize = 10; // 分页加载的数量
|
||||
const pagesContainerRef = ref();
|
||||
|
||||
const state = reactive<{
|
||||
completePagesLayout: any[];
|
||||
currentViewPageNumber: number;
|
||||
|
||||
pagingRenderedQuantity: number;
|
||||
pagingTargetQuantity: number;
|
||||
paperSize: any;
|
||||
|
||||
paperViewHeight: number;
|
||||
printConfig: any;
|
||||
printRenderInstances: any[];
|
||||
|
||||
processPagesLayout: any[];
|
||||
|
||||
totalRenderedQuantity: number;
|
||||
|
||||
watermarkConfig: any;
|
||||
}>({
|
||||
completePagesLayout: [], // 完整的页面架构
|
||||
currentViewPageNumber: 1, // 当前视图页码
|
||||
|
||||
pagingRenderedQuantity: 0,
|
||||
pagingTargetQuantity: 0,
|
||||
paperSize: null,
|
||||
|
||||
paperViewHeight: printA4PaperHeight,
|
||||
printConfig: null,
|
||||
printRenderInstances: [], // 打印渲染的实例
|
||||
|
||||
processPagesLayout: [], // 进程中页面架构
|
||||
|
||||
totalRenderedQuantity: 0,
|
||||
|
||||
watermarkConfig: {},
|
||||
});
|
||||
const { completePagesLayout, currentViewPageNumber, printConfig, processPagesLayout, watermarkConfig } = toRefs(state);
|
||||
|
||||
// 禁止向前翻页状态
|
||||
const disabledForward = computed(() => {
|
||||
return state.currentViewPageNumber <= 1;
|
||||
});
|
||||
|
||||
// 禁止向后翻页状态
|
||||
const disabledBackward = computed(() => {
|
||||
return state.currentViewPageNumber >= state.completePagesLayout.length;
|
||||
});
|
||||
|
||||
// 翻页 - 去首页
|
||||
function setFirstViewPageNumber() {
|
||||
state.currentViewPageNumber = 1;
|
||||
|
||||
handleScrollToPosition();
|
||||
}
|
||||
|
||||
// 翻页 - 去尾页
|
||||
function setLastViewPageNumber() {
|
||||
state.currentViewPageNumber = state.completePagesLayout.length;
|
||||
|
||||
handleScrollToPosition();
|
||||
}
|
||||
|
||||
// 翻页 - 前后单页控制
|
||||
function setSingleViewPageNumber(direction: 'next' | 'prev') {
|
||||
const maxPage = state.completePagesLayout.length;
|
||||
const currentPage = state.currentViewPageNumber;
|
||||
|
||||
// 根据方向计算目标页码
|
||||
const newPageNumber =
|
||||
direction === 'prev'
|
||||
? Math.max(currentPage - 1, 1) // 前一页,不能小于 1
|
||||
: Math.min(currentPage + 1, maxPage); // 下一页,不能大于总页数
|
||||
|
||||
// 如果页码发生变化,更新并滚动
|
||||
if (newPageNumber !== currentPage) {
|
||||
state.currentViewPageNumber = newPageNumber;
|
||||
handleScrollToPosition();
|
||||
}
|
||||
}
|
||||
|
||||
// 翻页 - 直接输入页码
|
||||
function setInputViewPageNumber() {
|
||||
const pageNumber = Number(state.currentViewPageNumber);
|
||||
|
||||
if (Number.isNaN(pageNumber) || pageNumber < 1) {
|
||||
state.currentViewPageNumber = 1;
|
||||
} else if (pageNumber > state.completePagesLayout?.length) {
|
||||
state.currentViewPageNumber = state.completePagesLayout.length;
|
||||
}
|
||||
|
||||
handleScrollToPosition();
|
||||
}
|
||||
|
||||
// 设置滚动的位置
|
||||
function handleScrollToPosition() {
|
||||
const targetScrollTop = state.paperViewHeight * (state.currentViewPageNumber - 1) + 28 * (state.currentViewPageNumber - 1);
|
||||
|
||||
pagesContainerRef.value.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top: targetScrollTop,
|
||||
});
|
||||
}
|
||||
|
||||
// 定义防抖后的函数
|
||||
const debouncedOnPagesContainerScroll = debounce(() => {
|
||||
const scrollTop = pagesContainerRef.value.scrollTop;
|
||||
|
||||
state.currentViewPageNumber = Math.ceil((scrollTop + 28) / (state.paperViewHeight + 28));
|
||||
}, 100); // 100 毫秒的防抖时间,可以根据需要调整
|
||||
|
||||
// 开启监听容器滚动事件
|
||||
function onPagesContainerScroll() {
|
||||
if (pagesContainerRef.value) {
|
||||
pagesContainerRef.value.addEventListener('scroll', debouncedOnPagesContainerScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭监听容器滚动事件
|
||||
function offPagesContainerScroll() {
|
||||
if (pagesContainerRef.value) {
|
||||
pagesContainerRef.value.removeEventListener('scroll', debouncedOnPagesContainerScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露 - 处理打印渲染
|
||||
async function handlePrintRender(printConfig: any, watermarkConfig: any) {
|
||||
resetState();
|
||||
|
||||
state.watermarkConfig = watermarkConfig ?? {};
|
||||
|
||||
const { zqUniverApi } = props ?? {};
|
||||
|
||||
try {
|
||||
if (!zqUniverApi) {
|
||||
emits('previewRenderProgress', {
|
||||
rendered: 0,
|
||||
total: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getLayoutsAsync(printConfig);
|
||||
const { pagesLayout, paperSize, printConfig: updatePrintConfig } = (res ?? {}) as any;
|
||||
|
||||
if (!pagesLayout.length) {
|
||||
emits('previewRenderProgress', {
|
||||
rendered: 0,
|
||||
total: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
state.completePagesLayout = Array.isArray(pagesLayout) ? [...pagesLayout] : [];
|
||||
state.printConfig = { ...updatePrintConfig };
|
||||
state.paperSize = { ...paperSize };
|
||||
|
||||
batchRenderPreviewPages();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获得布局的结果
|
||||
function getLayoutsAsync(config: any) {
|
||||
const { zqUniverApi } = props ?? {};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = zqUniverApi?.getSheetsPrint()?.getLayouts(config);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 批量加载预览页面
|
||||
function batchRenderPreviewPages() {
|
||||
const { completePagesLayout, processPagesLayout } = state;
|
||||
|
||||
state.pagingRenderedQuantity = 0;
|
||||
|
||||
// 全部都渲染完了
|
||||
if (processPagesLayout.length >= completePagesLayout.length) {
|
||||
state.pagingTargetQuantity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (completePagesLayout.length <= batchRenderSize) {
|
||||
state.pagingTargetQuantity = completePagesLayout.length;
|
||||
state.processPagesLayout = [...completePagesLayout];
|
||||
} else {
|
||||
const lastPageIndex = processPagesLayout.length;
|
||||
const batchData = completePagesLayout.slice(lastPageIndex, lastPageIndex + batchRenderSize) ?? [];
|
||||
|
||||
state.pagingTargetQuantity = batchData.length;
|
||||
state.processPagesLayout = [...processPagesLayout, ...batchData];
|
||||
}
|
||||
}
|
||||
|
||||
// 预览页面渲染回调
|
||||
function handlePreviewPageRendered() {
|
||||
state.pagingRenderedQuantity += 1;
|
||||
state.totalRenderedQuantity += 1;
|
||||
|
||||
emits('previewRenderProgress', {
|
||||
rendered: state.totalRenderedQuantity,
|
||||
total: state.completePagesLayout.length,
|
||||
});
|
||||
|
||||
if (state.pagingRenderedQuantity === state.pagingTargetQuantity && state.processPagesLayout.length < state.completePagesLayout.length) {
|
||||
setTimeout(batchRenderPreviewPages, 500);
|
||||
} else if (state.pagingRenderedQuantity === state.pagingTargetQuantity && state.processPagesLayout.length >= state.completePagesLayout.length) {
|
||||
nextTick(() => {
|
||||
const firstPageDom = document.getElementsByClassName('print-page-container')?.[0] as HTMLElement | undefined;
|
||||
|
||||
if (firstPageDom) {
|
||||
const styleHeight = firstPageDom?.style?.height;
|
||||
state.paperViewHeight = Number.parseInt(styleHeight);
|
||||
} else {
|
||||
state.paperViewHeight = printA4PaperHeight;
|
||||
}
|
||||
|
||||
// 绑定容器的滚动事件
|
||||
onPagesContainerScroll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露 - 浏览器打印
|
||||
function handleBrowserPrint() {
|
||||
emits('printRenderProgress', {
|
||||
rendered: 0,
|
||||
total: state.completePagesLayout.length,
|
||||
});
|
||||
|
||||
state.printRenderInstances = [];
|
||||
|
||||
const { zqUniverApi } = props ?? {};
|
||||
|
||||
const accessor = zqUniverApi?.getInjector();
|
||||
if (!accessor) {
|
||||
return;
|
||||
}
|
||||
|
||||
batchRenderPrintPages(accessor);
|
||||
}
|
||||
|
||||
// 批量加载打印页面
|
||||
function batchRenderPrintPages(accessor: any) {
|
||||
const { completePagesLayout, printConfig, printRenderInstances } = state;
|
||||
|
||||
// 已经全部渲染完成,直接返回
|
||||
if (printRenderInstances.length >= completePagesLayout.length) {
|
||||
createPrintEle();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取未渲染的页面数据
|
||||
const lastPageIndex = printRenderInstances.length;
|
||||
const batchData = completePagesLayout.slice(lastPageIndex, lastPageIndex + batchRenderSize) ?? [];
|
||||
|
||||
if (!batchData.length) {
|
||||
return printRenderInstances;
|
||||
}
|
||||
|
||||
// 批量创建渲染实例
|
||||
const renderInstances = batchData.map((pageLayout: any) => {
|
||||
// 这里要加水印配置
|
||||
const renderInstance = new ZqSheetsPrintUiService(accessor, pageLayout, printConfig, state.watermarkConfig);
|
||||
renderInstance.container.style.cssText = 'width: 100%; height: 100%;';
|
||||
return renderInstance;
|
||||
});
|
||||
|
||||
// 更新数据
|
||||
state.printRenderInstances.push(...renderInstances);
|
||||
|
||||
emits('printRenderProgress', {
|
||||
rendered: state.printRenderInstances.length,
|
||||
total: state.completePagesLayout.length,
|
||||
});
|
||||
|
||||
setTimeout(() => batchRenderPrintPages(accessor), 500);
|
||||
}
|
||||
|
||||
// 创建打印节点
|
||||
function createPrintEle() {
|
||||
const { completePagesLayout, paperSize, printConfig, printRenderInstances } = state;
|
||||
const { h: paperSizeHeight, w: paperSizeWidth = printA4PaperWidth } = paperSize ?? {};
|
||||
const { direction, paperType } = printConfig ?? {};
|
||||
const printStyle = getPrintPageStyle(paperType, direction);
|
||||
|
||||
const containerEle = document.createElement('div');
|
||||
containerEle.id = 'jnpfReportPrint';
|
||||
|
||||
printRenderInstances.forEach((renderInstance: any, index: number) => {
|
||||
const pageLayout = completePagesLayout[index];
|
||||
|
||||
if (pageLayout) {
|
||||
const { browserPreviewScale } = pageLayout ?? {};
|
||||
const scale = Math.round(browserPreviewScale * (printA4PaperWidth / paperSizeWidth) * 10) / 10;
|
||||
|
||||
const pageEle = document.createElement('div');
|
||||
pageEle.className = 'printContainer';
|
||||
|
||||
Object.assign(pageEle.style, {
|
||||
height: `${paperSizeHeight * scale}px`,
|
||||
width: `${paperSizeWidth * scale}px`,
|
||||
});
|
||||
|
||||
pageEle.append(renderInstance.container);
|
||||
containerEle.append(pageEle);
|
||||
} else {
|
||||
containerEle.append(renderInstance.container);
|
||||
}
|
||||
});
|
||||
|
||||
document.head.append(printStyle);
|
||||
document.body.append(containerEle);
|
||||
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
emits('browserPrintShow');
|
||||
|
||||
window.addEventListener('beforeprint', () => {
|
||||
printRenderInstances.forEach((renderInstance: any) => {
|
||||
renderInstance?.setDirty(true); // 设置脏状态(用于调整视口大小和滚动值的状态)
|
||||
renderInstance?.renderPage(); // 渲染页面
|
||||
renderInstance?.renderOnReady(); // 主要是在页面变换时确保页面内容的及时更新,并且做了订阅和销毁的管理。
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
printRenderInstances.forEach(async (renderInstance: any) => {
|
||||
await renderInstance.dispose();
|
||||
renderInstance = null;
|
||||
});
|
||||
printStyle.remove();
|
||||
containerEle.remove();
|
||||
});
|
||||
|
||||
window.print();
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露 - 重置
|
||||
function resetState() {
|
||||
if (pagesContainerRef.value) {
|
||||
pagesContainerRef.value.scrollTop = 0;
|
||||
}
|
||||
|
||||
state.completePagesLayout = [];
|
||||
state.processPagesLayout = [];
|
||||
|
||||
state.printRenderInstances = [];
|
||||
|
||||
state.pagingTargetQuantity = 0;
|
||||
state.pagingRenderedQuantity = 0;
|
||||
state.totalRenderedQuantity = 0;
|
||||
|
||||
state.printConfig = null;
|
||||
state.paperSize = null;
|
||||
state.paperViewHeight = printA4PaperHeight;
|
||||
|
||||
state.currentViewPageNumber = 1;
|
||||
|
||||
state.watermarkConfig = {};
|
||||
|
||||
offPagesContainerScroll();
|
||||
}
|
||||
|
||||
defineExpose({ handleBrowserPrint, handlePrintRender, resetState });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="print-render-content">
|
||||
<div class="tools-wrap">
|
||||
<span class="first-page" :class="[{ disabled: disabledForward }]" @click="setFirstViewPageNumber" title="首页">
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path
|
||||
d="M507.733333 490.666667L768 230.4 704 170.666667 384 490.666667l320 320 59.733333-59.733334-256-260.266666zM341.333333 170.666667H256v640h85.333333V170.666667z" />
|
||||
</svg>
|
||||
<em>首页</em>
|
||||
</span>
|
||||
<span class="prev-page" :class="[{ disabled: disabledForward }]" @click="setSingleViewPageNumber('prev')" title="上一页" style="margin-right: 12px">
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path
|
||||
d="M473.6 490.666667L789.333333 170.666667 853.333333 230.4l-260.266666 260.266667 260.266666 260.266666-64 59.733334-315.733333-320z m-302.933333 0L490.666667 170.666667l59.733333 59.733333-260.266667 260.266667 260.266667 260.266666-59.733333 59.733334L170.666667 490.666667z" />
|
||||
</svg>
|
||||
<em>上一页</em>
|
||||
</span>
|
||||
<input class="target-page" v-model="currentViewPageNumber" type="number" @blur="setInputViewPageNumber" min="1" :max="completePagesLayout.length" />
|
||||
<span class="total-page-number">
|
||||
<em>/</em>
|
||||
<strong>{{ completePagesLayout.length }}</strong>
|
||||
</span>
|
||||
<span class="next-page" :class="[{ disabled: disabledBackward }]" @click="setSingleViewPageNumber('next')" title="下一页" style="margin-left: 12px">
|
||||
<em>下一页</em>
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path
|
||||
d="M550.4 490.666667L230.4 170.666667 170.666667 230.4l260.266666 260.266667L170.666667 750.933333 230.4 810.666667l320-320z m298.666667 0L533.333333 170.666667 469.333333 230.4l260.266667 260.266667-260.266667 260.266666 59.733334 59.733334 320-320z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="last-page" :class="[{ disabled: disabledBackward }]" @click="setLastViewPageNumber" title="尾页">
|
||||
<em>尾页</em>
|
||||
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
|
||||
<path
|
||||
d="M516.266667 490.666667L256 230.4 315.733333 170.666667l320 320L315.733333 810.666667 256 750.933333l260.266667-260.266666zM678.4 170.666667h85.333333v640h-85.333333V170.666667z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div ref="pagesContainerRef" class="print-render-pages-content">
|
||||
<template v-for="pageLayout in processPagesLayout" :key="pageLayout?.layoutId">
|
||||
<ZqUniverPrintRenderPage
|
||||
:zq-univer-api="props.zqUniverApi"
|
||||
:page-layout="pageLayout"
|
||||
:print-config="printConfig"
|
||||
:watermark-config="watermarkConfig"
|
||||
@page-rendered="handlePreviewPageRendered" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.print-render-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
.tools-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-width: 500px;
|
||||
padding: 10px 0;
|
||||
user-select: none;
|
||||
|
||||
.first-page,
|
||||
.prev-page,
|
||||
.next-page,
|
||||
.last-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
//width: 32px;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
color: rgb(0 0 0 / 88%);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
.icon {
|
||||
fill: rgb(0 0 0 / 88%);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: rgb(0 0 0 / 25%);
|
||||
cursor: not-allowed;
|
||||
|
||||
.icon {
|
||||
fill: rgb(0 0 0 / 25%);
|
||||
}
|
||||
}
|
||||
|
||||
em {
|
||||
margin-left: 2px;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgb(0 0 0 / 6%);
|
||||
}
|
||||
}
|
||||
|
||||
.target-page {
|
||||
box-sizing: border-box;
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
color: rgb(0 0 0 / 88%);
|
||||
text-align: center;
|
||||
appearance: textfield;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
margin: 0;
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.total-page-number {
|
||||
display: flex;
|
||||
line-height: 32px;
|
||||
color: rgb(0 0 0 / 88%);
|
||||
|
||||
em {
|
||||
margin: 0 4px;
|
||||
font-style: normal;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.print-render-pages-content {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
.print-page-container:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark,
|
||||
.dark[data-theme='custom'],
|
||||
.dark[data-theme='default'] {
|
||||
.print-render-content {
|
||||
color: #fff;
|
||||
|
||||
.tools-wrap {
|
||||
.first-page,
|
||||
.prev-page,
|
||||
.next-page,
|
||||
.last-page,
|
||||
.total-page-number {
|
||||
color: #fff;
|
||||
|
||||
em {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: rgb(255 255 255 / 70%);
|
||||
|
||||
em {
|
||||
color: rgb(255 255 255 / 70%);
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: rgb(255 255 255 / 70%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,320 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, reactive, ref, toRefs, unref } from 'vue';
|
||||
|
||||
import type { IWorkbookData } from '@univerjs/core';
|
||||
import { SetWorksheetActiveOperation } from '@univerjs/sheets';
|
||||
import { $t } from '@vben/locales';
|
||||
import { ElButton, ElDialog } from 'element-plus';
|
||||
|
||||
import ZqUniverDesign from '../Design/index.vue';
|
||||
import { DefaultPrintConfig } from '../Design/univer/utils/define';
|
||||
import ReportDialogHeader from '../shared/ReportDialogHeader.vue';
|
||||
import ZqUniverPrintForm from './Form/index.vue';
|
||||
import ZqUniverPrintRender from './Render/index.vue';
|
||||
|
||||
defineOptions({ name: 'ZqUniverPrint' });
|
||||
|
||||
const zqUniverDesignPrintRef = ref();
|
||||
const zqUniverPrintRenderRef = ref();
|
||||
|
||||
const defaultRenderProgressTip = ' ';
|
||||
const defaultFreezeStatus = {
|
||||
colHasFreeze: false,
|
||||
rowHasFreeze: false,
|
||||
};
|
||||
|
||||
interface State {
|
||||
internalOpen: boolean;
|
||||
zqUniverAPI: any;
|
||||
loading: boolean;
|
||||
printRenderTip: string;
|
||||
sheetFreezeStatus: any;
|
||||
watermarkConfig: Record<string, any>;
|
||||
reportName: string;
|
||||
reportCode: string;
|
||||
}
|
||||
|
||||
const state = reactive<State>({
|
||||
internalOpen: false,
|
||||
zqUniverAPI: null,
|
||||
loading: false,
|
||||
printRenderTip: defaultRenderProgressTip,
|
||||
sheetFreezeStatus: { ...defaultFreezeStatus },
|
||||
watermarkConfig: {},
|
||||
reportName: '',
|
||||
reportCode: '',
|
||||
});
|
||||
const {
|
||||
internalOpen,
|
||||
zqUniverAPI,
|
||||
loading,
|
||||
printRenderTip,
|
||||
sheetFreezeStatus,
|
||||
watermarkConfig,
|
||||
reportName,
|
||||
reportCode,
|
||||
} = toRefs(state);
|
||||
|
||||
function handleCreatePrintUnit(data: {
|
||||
defaultActiveSheetId?: string;
|
||||
snapshot: IWorkbookData;
|
||||
watermarkConfig?: Record<string, any>;
|
||||
reportName?: string;
|
||||
reportCode?: string;
|
||||
}) {
|
||||
state.internalOpen = true;
|
||||
state.printRenderTip = defaultRenderProgressTip;
|
||||
state.loading = true;
|
||||
state.watermarkConfig = data?.watermarkConfig ?? {};
|
||||
state.reportName = data?.reportName ?? '';
|
||||
state.reportCode = data?.reportCode ?? '';
|
||||
|
||||
void initPrintUnit(data);
|
||||
}
|
||||
|
||||
async function initPrintUnit(data: {
|
||||
defaultActiveSheetId?: string;
|
||||
snapshot: IWorkbookData;
|
||||
}) {
|
||||
const { defaultActiveSheetId, snapshot } = data ?? {};
|
||||
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
try {
|
||||
const result = await unref(zqUniverDesignPrintRef)?.handleCreateDesignUnit({
|
||||
defaultActiveSheetId,
|
||||
loading: false,
|
||||
mode: 'print',
|
||||
snapshot,
|
||||
uiHeader: false,
|
||||
uiFooter: false,
|
||||
uiContextMenu: false,
|
||||
});
|
||||
state.zqUniverAPI = result?.zqUniverAPI ?? null;
|
||||
|
||||
if (!state.zqUniverAPI) {
|
||||
state.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state.zqUniverAPI.getHooks()?.onSteady(() => {
|
||||
const subUnitId =
|
||||
defaultActiveSheetId ?? state.zqUniverAPI?.getActiveWorkbook()?.getActiveSheet()?.getSheetId();
|
||||
|
||||
const { xSplit = 0, ySplit = 0 } = snapshot?.sheets?.[subUnitId ?? '']?.freeze ?? {};
|
||||
state.sheetFreezeStatus = {
|
||||
colHasFreeze: xSplit > 0,
|
||||
rowHasFreeze: ySplit > 0,
|
||||
};
|
||||
|
||||
unref(zqUniverPrintRenderRef)?.handlePrintRender(
|
||||
JSON.parse(JSON.stringify(DefaultPrintConfig)),
|
||||
JSON.parse(JSON.stringify(state.watermarkConfig)),
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('initPrintUnit failed:', error);
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangePrintConfig(config: any) {
|
||||
state.printRenderTip = defaultRenderProgressTip;
|
||||
state.loading = true;
|
||||
unref(zqUniverPrintRenderRef)?.handlePrintRender(
|
||||
JSON.parse(JSON.stringify(config)),
|
||||
JSON.parse(JSON.stringify(state.watermarkConfig)),
|
||||
);
|
||||
}
|
||||
|
||||
function getPreviewRenderProgress(res: any) {
|
||||
const { rendered, total } = res ?? {};
|
||||
state.printRenderTip = `共${total}页,已加载${rendered}页`;
|
||||
|
||||
if (rendered === total) {
|
||||
state.loading = false;
|
||||
|
||||
const activeWorkbook = zqUniverAPI.value?.getActiveWorkbook();
|
||||
const unitId = activeWorkbook?.getId();
|
||||
const sheets = activeWorkbook.save().sheets;
|
||||
const sheetKeys = Object.keys(sheets);
|
||||
const activeSubUnitId = activeWorkbook?.getActiveSheet()?.getSheetId();
|
||||
|
||||
function autoRenderAllSheets(index: number) {
|
||||
if (index >= sheetKeys?.length) {
|
||||
zqUniverAPI.value?.executeCommand(SetWorksheetActiveOperation.id, {
|
||||
subUnitId: activeSubUnitId,
|
||||
unitId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const sheetKey = sheetKeys[index];
|
||||
zqUniverAPI.value?.executeCommand(SetWorksheetActiveOperation.id, {
|
||||
subUnitId: sheetKey,
|
||||
unitId,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
autoRenderAllSheets(index + 1);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
autoRenderAllSheets(0);
|
||||
}
|
||||
}
|
||||
|
||||
function handleBrowserPrint() {
|
||||
state.printRenderTip = defaultRenderProgressTip;
|
||||
state.loading = true;
|
||||
unref(zqUniverPrintRenderRef)?.handleBrowserPrint();
|
||||
}
|
||||
|
||||
function getPrintRenderProgress(res: any) {
|
||||
const { rendered, total } = res ?? {};
|
||||
state.printRenderTip = `共${total}页,已准备${rendered}页`;
|
||||
if (rendered === total) {
|
||||
state.printRenderTip = '整理中...';
|
||||
}
|
||||
}
|
||||
|
||||
function handleBrowserShow() {
|
||||
state.loading = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
resetUniverState();
|
||||
unref(zqUniverDesignPrintRef)?.handleDisposeUnit();
|
||||
state.internalOpen = false;
|
||||
}
|
||||
|
||||
function resetUniverState() {
|
||||
state.zqUniverAPI = null;
|
||||
state.loading = false;
|
||||
state.printRenderTip = defaultRenderProgressTip;
|
||||
state.sheetFreezeStatus = JSON.parse(JSON.stringify(defaultFreezeStatus));
|
||||
state.reportName = '';
|
||||
state.reportCode = '';
|
||||
unref(zqUniverPrintRenderRef)?.resetState();
|
||||
}
|
||||
|
||||
defineExpose({ handleCreatePrintUnit });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="internalOpen"
|
||||
class="zq-print-full-dialog"
|
||||
fullscreen
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
:z-index="4000"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<div class="bg-background flex h-full min-h-0 flex-col">
|
||||
<ReportDialogHeader
|
||||
:report-name="reportName"
|
||||
:report-code="reportCode"
|
||||
:fallback-title="$t('report-manager.print.previewTitle')"
|
||||
>
|
||||
<template #actions>
|
||||
<ElButton @click="handleBrowserPrint">
|
||||
{{ $t('report-manager.print.browserPrint') }}
|
||||
</ElButton>
|
||||
<ElButton @click="handleCancel">
|
||||
{{ $t('common.close') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ReportDialogHeader>
|
||||
|
||||
<main
|
||||
v-loading="loading"
|
||||
class="content-main m-3 min-h-0 flex-1 overflow-hidden rounded-lg"
|
||||
:element-loading-text="printRenderTip"
|
||||
>
|
||||
<div class="print-container">
|
||||
<div class="print-design-content">
|
||||
<ZqUniverDesign v-if="internalOpen" ref="zqUniverDesignPrintRef" />
|
||||
</div>
|
||||
|
||||
<div class="print-main-container">
|
||||
<ZqUniverPrintRender
|
||||
ref="zqUniverPrintRenderRef"
|
||||
:zq-univer-api="zqUniverAPI"
|
||||
@preview-render-progress="getPreviewRenderProgress"
|
||||
@print-render-progress="getPrintRenderProgress"
|
||||
@browser-print-show="handleBrowserShow"
|
||||
/>
|
||||
|
||||
<ZqUniverPrintForm
|
||||
v-if="internalOpen"
|
||||
:default-print-config="DefaultPrintConfig"
|
||||
:sheet-freeze-status="sheetFreezeStatus"
|
||||
@change="handleChangePrintConfig"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.zq-print-full-dialog.el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.el-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.zq-print-full-dialog {
|
||||
.content-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.print-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
.print-design-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
|
||||
.zq-univer-design-content {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.print-main-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color-page, #f2f3f4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { App } from 'vue';
|
||||
|
||||
import ZqUniver from './Design/index.vue';
|
||||
import ZqUniverCellEchart from './Design/univer/components/Echart/cell.vue';
|
||||
import ZqUniverFloatEchartVM from './Design/univer/components/Echart/floatVM.vue';
|
||||
import ZqUniverPrint from './Print/index.vue';
|
||||
import ReportDialogHeader from './shared/ReportDialogHeader.vue';
|
||||
|
||||
ZqUniver.install = (app: App) => app.component(ZqUniver.name as string, ZqUniver);
|
||||
ZqUniverPrint.install = (app: App) =>
|
||||
app.component(ZqUniverPrint.name as string, ZqUniverPrint);
|
||||
ZqUniverFloatEchartVM.install = (app: App) =>
|
||||
app.component(ZqUniverFloatEchartVM.name as string, ZqUniverFloatEchartVM);
|
||||
ZqUniverCellEchart.install = (app: App) =>
|
||||
app.component(ZqUniverCellEchart.name as string, ZqUniverCellEchart);
|
||||
|
||||
export { ZqUniver, ZqUniverCellEchart, ZqUniverFloatEchartVM, ZqUniverPrint, ReportDialogHeader };
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.component(ZqUniver.name as string, ZqUniver);
|
||||
app.component(ZqUniverPrint.name as string, ZqUniverPrint);
|
||||
app.component(ZqUniverFloatEchartVM.name as string, ZqUniverFloatEchartVM);
|
||||
app.component(ZqUniverCellEchart.name as string, ZqUniverCellEchart);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
reportName?: string;
|
||||
reportCode?: string;
|
||||
fallbackTitle?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="bg-background-deep mx-3 mt-3 flex h-14 shrink-0 items-center justify-between rounded-lg px-4 shadow-sm"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="text-foreground truncate font-medium">
|
||||
{{ reportName || fallbackTitle }}
|
||||
</div>
|
||||
<div v-if="reportCode" class="text-muted-foreground truncate text-xs">
|
||||
{{ reportCode }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shrink-0 gap-2">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Configure and register global directives
|
||||
*/
|
||||
import type { App } from 'vue';
|
||||
|
||||
import { setupLoadingDirective } from './loading';
|
||||
|
||||
export function setupGlobDirectives(app: App) {
|
||||
setupLoadingDirective(app);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
|
||||
import { createLoading } from '../components/Loading';
|
||||
|
||||
const loadingDirective: Directive = {
|
||||
mounted(el, binding) {
|
||||
const tip = el.getAttribute('loading-tip');
|
||||
const background = el.getAttribute('loading-background');
|
||||
const size = el.getAttribute('loading-size');
|
||||
const fullscreen = !!binding.modifiers.fullscreen;
|
||||
const instance = createLoading(
|
||||
{
|
||||
absolute: !fullscreen,
|
||||
background,
|
||||
loading: !!binding.value,
|
||||
size: size || 'large',
|
||||
tip,
|
||||
},
|
||||
fullscreen ? document.body : el,
|
||||
);
|
||||
el.instance = instance;
|
||||
},
|
||||
unmounted(el) {
|
||||
el?.instance?.close();
|
||||
},
|
||||
updated(el, binding) {
|
||||
const instance = el.instance;
|
||||
if (!instance) return;
|
||||
instance.setTip(el.getAttribute('loading-tip'));
|
||||
if (binding.oldValue !== binding.value) {
|
||||
instance.setLoading?.(binding.value && !instance.loading);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export function setupLoadingDirective(app: App) {
|
||||
app.directive('loading', loadingDirective);
|
||||
}
|
||||
|
||||
export default loadingDirective;
|
||||
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
ZqUniver,
|
||||
ZqUniverCellEchart,
|
||||
ZqUniverFloatEchartVM,
|
||||
ZqUniverPrint,
|
||||
ReportDialogHeader,
|
||||
} from './components/Report/index';
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user