|
@@ -0,0 +1,397 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="ssjson-report-viewer" v-loading="loading" element-loading-text="正在加载模板...">
|
|
|
|
|
+ <OptimizedTemplatePreview ref="previewRef" @ready="handleReady" @save="handleSave" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, onBeforeUnmount } from 'vue'
|
|
|
|
|
+import * as GC from '@grapecity-software/spread-sheets'
|
|
|
|
|
+import {
|
|
|
|
|
+ OptimizedTemplatePreview,
|
|
|
|
|
+ type SpreadWorkbookConfig
|
|
|
|
|
+} from '@synboy/spreadjs-custom-render'
|
|
|
|
|
+import '@synboy/spreadjs-custom-render/dist/style.css'
|
|
|
|
|
+import { getStandardTemplateInfo } from '@/api/pressure2/standard/template'
|
|
|
|
|
+import { ExcelApi } from '@/api/pressure2/excel'
|
|
|
|
|
+import { DynamicTbValApi } from '@/api/pressure2/dynamictbval'
|
|
|
|
|
+import { buildFileUrl } from '@/utils'
|
|
|
|
|
+import axios from 'axios'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'SsjsonReportViewer' })
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits<{
|
|
|
|
|
+ ready: []
|
|
|
|
|
+ save: [data: Record<string, any>]
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const previewRef = ref<InstanceType<typeof OptimizedTemplatePreview>>()
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// SpreadJS 授权(仅设置一次,避免反复赋值)
|
|
|
|
|
+const licenseKey = import.meta.env.VITE_SPREADJS_LICENSE_KEY
|
|
|
|
|
+if (licenseKey) {
|
|
|
|
|
+ GC.Spread.Sheets.LicenseKey = licenseKey
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 临时 Workbook 宿主元素(懒创建,复用避免反复创建销毁)
|
|
|
|
|
+let tempHost: HTMLDivElement | null = null
|
|
|
|
|
+const getTempHost = (): HTMLDivElement => {
|
|
|
|
|
+ if (!tempHost) {
|
|
|
|
|
+ tempHost = document.createElement('div')
|
|
|
|
|
+ tempHost.style.cssText =
|
|
|
|
|
+ 'position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;overflow:hidden;'
|
|
|
|
|
+ document.body.appendChild(tempHost)
|
|
|
|
|
+ }
|
|
|
|
|
+ return tempHost
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface LoadOptions {
|
|
|
|
|
+ formData?: Record<string, any>
|
|
|
|
|
+ showToolbar?: boolean
|
|
|
|
|
+ readonly?: boolean
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 解析 config 中所有字符串形式的 style 引用(命名样式)
|
|
|
|
|
+ *
|
|
|
|
|
+ * SpreadJS toJSON() 输出中,cell.style / table.headerStyle 等字段
|
|
|
|
|
+ * 可能是字符串(如 '__builtInStyle1'),引用 config.namedStyles 中的命名样式。
|
|
|
|
|
+ * OptimizedTemplatePreview 的 convertStyle 无法处理字符串,会导致样式全部丢失。
|
|
|
|
|
+ * 此函数把字符串引用替换为命名样式对象的深拷贝,保留完整样式信息。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 不调用 OptimizedTemplateExtractor,避免:
|
|
|
|
|
+ * 1. normalizeCellStyle 在字符串上设属性导致 foreColor 报错
|
|
|
|
|
+ * 2. normalizeCellStyle 的 fontSize 单位转换等处理与 convertStyle 重复或冲突
|
|
|
|
|
+ * (convertStyle 自身已做 pt→px 转换)
|
|
|
|
|
+ */
|
|
|
|
|
+const resolveNamedStyleRefs = (config: SpreadWorkbookConfig): void => {
|
|
|
|
|
+ const namedStyles = (config as any).namedStyles as any[] | undefined
|
|
|
|
|
+ if (!namedStyles || !namedStyles.length) return
|
|
|
|
|
+
|
|
|
|
|
+ // 建立命名样式映射(支持 parentName 继承链)
|
|
|
|
|
+ const styleMap = new Map<string, any>()
|
|
|
|
|
+ namedStyles.forEach(ns => {
|
|
|
|
|
+ if (ns.name) styleMap.set(ns.name, ns)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 解析单个 style 引用:字符串 → 沿 parentName 链合并后的深拷贝对象
|
|
|
|
|
+ const resolve = (style: any): any => {
|
|
|
|
|
+ if (typeof style !== 'string') return style
|
|
|
|
|
+ const named = styleMap.get(style)
|
|
|
|
|
+ if (!named) return null
|
|
|
|
|
+ // 沿 parentName 链合并(父在前,子覆盖父)
|
|
|
|
|
+ const chain: any[] = []
|
|
|
|
|
+ let current: any = named
|
|
|
|
|
+ const visited = new Set<string>()
|
|
|
|
|
+ while (current && current.name && !visited.has(current.name)) {
|
|
|
|
|
+ visited.add(current.name)
|
|
|
|
|
+ chain.unshift(current)
|
|
|
|
|
+ const parentName: string | undefined = current.parentName
|
|
|
|
|
+ current = parentName ? styleMap.get(parentName) : undefined
|
|
|
|
|
+ }
|
|
|
|
|
+ const merged: Record<string, any> = {}
|
|
|
|
|
+ chain.forEach(ns => {
|
|
|
|
|
+ Object.keys(ns).forEach(key => {
|
|
|
|
|
+ if (key === 'name' || key === 'parentName') return
|
|
|
|
|
+ const val = ns[key]
|
|
|
|
|
+ if (val !== null && val !== undefined) {
|
|
|
|
|
+ merged[key] = val
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ return merged
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 统一处理:resolve 后要么是对象要么删除
|
|
|
|
|
+ const resolveField = (obj: any, field: string) => {
|
|
|
|
|
+ if (obj?.[field]) {
|
|
|
|
|
+ const resolved = resolve(obj[field])
|
|
|
|
|
+ if (resolved) {
|
|
|
|
|
+ obj[field] = resolved
|
|
|
|
|
+ } else {
|
|
|
|
|
+ delete obj[field]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Object.values(config.sheets || {}).forEach((sheet: any) => {
|
|
|
|
|
+ // 单元格样式
|
|
|
|
|
+ const dataTable = sheet.data?.dataTable
|
|
|
|
|
+ if (dataTable) {
|
|
|
|
|
+ Object.values(dataTable).forEach((row: any) => {
|
|
|
|
|
+ Object.values(row).forEach((cell: any) => {
|
|
|
|
|
+ resolveField(cell, 'style')
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ // 表格样式
|
|
|
|
|
+ sheet.tables?.forEach((table: any) => {
|
|
|
|
|
+ resolveField(table, 'headerStyle')
|
|
|
|
|
+ resolveField(table, 'dataStyle')
|
|
|
|
|
+ resolveField(table, 'alternateRowStyle')
|
|
|
|
|
+ table.columns?.forEach((col: any) => {
|
|
|
|
|
+ resolveField(col, 'headerStyle')
|
|
|
|
|
+ resolveField(col, 'dataStyle')
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ // 行样式
|
|
|
|
|
+ sheet.rows?.forEach((row: any) => resolveField(row, 'style'))
|
|
|
|
|
+ // 列样式
|
|
|
|
|
+ sheet.columns?.forEach((col: any) => resolveField(col, 'style'))
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 根据模板ID加载标准模板(.sjs/.ssjson 均可),自动转换为 SpreadWorkbookConfig
|
|
|
|
|
+ * 新版后端只有 fileUrl,没有 quickEntryJson 字段,因此需要本地用 SpreadJS 解析后提取
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param templateId 标准模板ID
|
|
|
|
|
+ * @param options 渲染选项
|
|
|
|
|
+ */
|
|
|
|
|
+const loadByTemplateId = async (templateId: string, options: LoadOptions = {}) => {
|
|
|
|
|
+ if (!templateId) {
|
|
|
|
|
+ ElMessage.warning('当前检验项目没有模板ID')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const templateRes = await getStandardTemplateInfo({ id: templateId })
|
|
|
|
|
+ if (!templateRes || !templateRes.fileUrl) {
|
|
|
|
|
+ ElMessage.warning('未找到模板文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const fileUrl = buildFileUrl(templateRes.fileUrl)
|
|
|
|
|
+ // 模板文件可能是 .sjs 二进制或 .ssjson JSON,统一用 blob 拉取后交给 SpreadJS 识别
|
|
|
|
|
+ const response = await axios.get(fileUrl, { responseType: 'blob' })
|
|
|
|
|
+ const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
|
|
|
|
+
|
|
|
|
|
+ // 用临时 workbook 加载 blob,再 toJSON 转为组件所需的 SpreadWorkbookConfig
|
|
|
|
|
+ const host = getTempHost()
|
|
|
|
|
+ const workbook = new GC.Spread.Sheets.Workbook(host)
|
|
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
|
|
+ workbook.open(
|
|
|
|
|
+ blob,
|
|
|
|
|
+ () => resolve(),
|
|
|
|
|
+ (err: any) => reject(err)
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const config = workbook.toJSON() as SpreadWorkbookConfig
|
|
|
|
|
+ workbook.destroy()
|
|
|
|
|
+
|
|
|
|
|
+ // 解析命名样式字符串引用,保留完整样式(不调用 OptimizedTemplateExtractor,
|
|
|
|
|
+ // 避免 normalizeCellStyle 在字符串上设属性报错,以及与 convertStyle 重复转换)
|
|
|
|
|
+ resolveNamedStyleRefs(config)
|
|
|
|
|
+
|
|
|
|
|
+ previewRef.value?.register({
|
|
|
|
|
+ config,
|
|
|
|
|
+ formData: options.formData || {},
|
|
|
|
|
+ showToolbar: options.showToolbar ?? true,
|
|
|
|
|
+ readonly: options.readonly ?? false
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载 ssjson 模板失败:', error)
|
|
|
|
|
+ ElMessage.error('加载 ssjson 模板失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 直接传入已处理好的 SpreadWorkbookConfig 进行渲染
|
|
|
|
|
+ * 适用于配置已经过 extract 处理、或从其他来源获取的场景
|
|
|
|
|
+ */
|
|
|
|
|
+const loadFromConfig = (
|
|
|
|
|
+ config: SpreadWorkbookConfig,
|
|
|
|
|
+ formData: Record<string, any> = {},
|
|
|
|
|
+ options: Omit<LoadOptions, 'formData'> = {}
|
|
|
|
|
+) => {
|
|
|
|
|
+ previewRef.value?.register({
|
|
|
|
|
+ config,
|
|
|
|
|
+ formData,
|
|
|
|
|
+ showToolbar: options.showToolbar ?? true,
|
|
|
|
|
+ readonly: options.readonly ?? false
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 表格行样式同步(参考 SpreadViewer.handleTableRowsAfterDataBind)
|
|
|
|
|
+// 数据绑定后,将第一行(模板行)的样式、行高复制到所有自动扩展的数据行
|
|
|
|
|
+const tableCopyOptions = GC.Spread.Sheets.CopyToOptions.style | GC.Spread.Sheets.CopyToOptions.formula
|
|
|
|
|
+const handleTableRowsAfterDataBind = (sheets: any[]) => {
|
|
|
|
|
+ for (const sheet of sheets) {
|
|
|
|
|
+ const tables = sheet.tables?.all() || []
|
|
|
|
|
+ for (const table of tables) {
|
|
|
|
|
+ const range = table.range()
|
|
|
|
|
+ const { row, rowCount, col, colCount } = range
|
|
|
|
|
+ if (rowCount <= 1) continue
|
|
|
|
|
+ // 批量设置自动换行
|
|
|
|
|
+ sheet.getRange(row, col, rowCount, colCount).wordWrap(true)
|
|
|
|
|
+ // 复制第一行样式和行高到所有数据行
|
|
|
|
|
+ const firstRowHeight = sheet.getRowHeight(row)
|
|
|
|
|
+ for (let r = 1; r < rowCount; r++) {
|
|
|
|
|
+ sheet.copyTo(row, col, row + r, col, 1, colCount, tableCopyOptions)
|
|
|
|
|
+ sheet.setRowHeight(row + r, firstRowHeight)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 根据 templateId + refId 加载后端处理好的 SJS(含图片、可编辑字段、续页)
|
|
|
|
|
+ * 参考 SpreadViewer.vue 的核心数据准备逻辑:
|
|
|
|
|
+ * 1. 调用 ExcelApi.excel 获取后端处理好的 SJS blob(已含图片、续页、可编辑字段)
|
|
|
|
|
+ * 2. 调用 DynamicTbValApi 获取实例数据,作为 formData 传给渲染组件
|
|
|
|
|
+ * (OptimizedTemplatePreview 自身根据 bindingPath + formData 渲染,无需在 workbook 上 setDataSource)
|
|
|
|
|
+ * 3. 用 extractor 提取配置(保留背景图片)后渲染
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param initData 同 SpreadViewer 的 initData(含 templateId、refId、dataSource 等)
|
|
|
|
|
+ * @param options 渲染选项
|
|
|
|
|
+ */
|
|
|
|
|
+const loadByRefId = async (
|
|
|
|
|
+ initData: Record<string, any>,
|
|
|
|
|
+ options: LoadOptions = {}
|
|
|
|
|
+) => {
|
|
|
|
|
+ const templateId = initData.templateId
|
|
|
|
|
+ const refId = initData.refId
|
|
|
|
|
+ if (!templateId || !refId) {
|
|
|
|
|
+ ElMessage.warning('缺少模板ID或实例ID,无法加载 ssjson')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 调用后端获取处理好的 SJS(已含图片、续页、可编辑字段)
|
|
|
|
|
+ const sjsBlob = await ExcelApi.excel({ templateId, refId })
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 用临时 workbook 打开
|
|
|
|
|
+ const host = getTempHost()
|
|
|
|
|
+ const workbook = new GC.Spread.Sheets.Workbook(host)
|
|
|
|
|
+ await new Promise<void>((resolve, reject) => {
|
|
|
|
|
+ workbook.open(
|
|
|
|
|
+ sjsBlob,
|
|
|
|
|
+ () => resolve(),
|
|
|
|
|
+ (err: any) => reject(err)
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 获取实例数据并构建 formData(参考 SpreadViewer 的 sheetData 构建逻辑)
|
|
|
|
|
+ // OptimizedTemplatePreview 根据 bindingPath + formData 渲染,不需要在 workbook 上 setDataSource
|
|
|
|
|
+ let sheetData: Record<string, any> = {}
|
|
|
|
|
+ const res = await DynamicTbValApi.getDynamicTbInsAndValByRefId(initData)
|
|
|
|
|
+ if (res.dynamicTbValRespVOList?.length) {
|
|
|
|
|
+ res.dynamicTbValRespVOList.forEach((i: any) => {
|
|
|
|
|
+ const val = i.valValue
|
|
|
|
|
+ if (typeof val === 'string') {
|
|
|
|
|
+ const trimmed = val.trim()
|
|
|
|
|
+ // 后端已将图片路径值处理为背景图片,跳过避免文字覆盖
|
|
|
|
|
+ if (trimmed.endsWith('.jpg') || trimmed.endsWith('.png')) {
|
|
|
|
|
+ // 跳过
|
|
|
|
|
+ } else if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
|
|
|
+ if (i.colCode === 'Illustration') {
|
|
|
|
|
+ sheetData[i.colCode] = null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try { sheetData[i.colCode] = JSON.parse(val) } catch { sheetData[i.colCode] = val }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (val == 'false' || val == 'true') {
|
|
|
|
|
+ sheetData[i.colCode] = val == 'true' ? 'true' : null
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sheetData[i.colCode] = val
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sheetData[i.colCode] = val
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ // 优先使用 initData.dataSource(外部传入),否则用实例数据
|
|
|
|
|
+ const formData = initData.dataSource || sheetData
|
|
|
|
|
+
|
|
|
|
|
+ // 4. toJSON 转为 SpreadWorkbookConfig(不调用 OptimizedTemplateExtractor,
|
|
|
|
|
+ // 避免 normalizeCellStyle 在字符串 style 引用上报 foreColor 错误,
|
|
|
|
|
+ // 以及与 convertStyle 重复做 fontSize 单位转换导致样式错乱)
|
|
|
|
|
+ // 表格行扩展、数据填充均由 OptimizedTemplatePreview 根据 bindingPath + formData 完成
|
|
|
|
|
+ const config = workbook.toJSON() as SpreadWorkbookConfig
|
|
|
|
|
+
|
|
|
|
|
+ // 4.1 从 live workbook 读取打印方向,注入到 config
|
|
|
|
|
+ // 原因:workbook.toJSON() 不一定输出 orientation 字段,
|
|
|
|
|
+ // 但 sheet.printInfo().orientation() 可以从 live 对象获取
|
|
|
|
|
+ if (config.sheets) {
|
|
|
|
|
+ const sheetNames = Object.keys(config.sheets)
|
|
|
|
|
+ sheetNames.forEach((sheetName) => {
|
|
|
|
|
+ const sheet = workbook.getSheetFromName(sheetName)
|
|
|
|
|
+ if (sheet) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const printInfo = sheet.printInfo()
|
|
|
|
|
+ const orientation = printInfo?.orientation?.()
|
|
|
|
|
+ if (orientation !== undefined && orientation !== null) {
|
|
|
|
|
+ const sheetConfig = config.sheets[sheetName] as any
|
|
|
|
|
+ if (!sheetConfig.printInfo) {
|
|
|
|
|
+ sheetConfig.printInfo = {}
|
|
|
|
|
+ }
|
|
|
|
|
+ // SpreadJS PrintOrientation: 0 = portrait, 1 = landscape
|
|
|
|
|
+ sheetConfig.printInfo.orientation = orientation
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // 忽略读取失败的 sheet
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ workbook.destroy()
|
|
|
|
|
+
|
|
|
|
|
+ // 4.2 解析命名样式字符串引用,保留完整样式
|
|
|
|
|
+ resolveNamedStyleRefs(config)
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 传给预览组件(formData 提供数据,config 提供模板结构 + bindingPath)
|
|
|
|
|
+ previewRef.value?.register({
|
|
|
|
|
+ config,
|
|
|
|
|
+ formData,
|
|
|
|
|
+ showToolbar: options.showToolbar ?? true,
|
|
|
|
|
+ readonly: options.readonly ?? false
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载 ssjson (refId) 失败:', error)
|
|
|
|
|
+ ElMessage.error('加载 ssjson 失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 以下方法透传给内部的 OptimizedTemplatePreview,便于父组件调用
|
|
|
|
|
+const getBoundFormData = () => previewRef.value?.getBoundFormData?.()
|
|
|
|
|
+const setFormData = (data: Record<string, any>) => previewRef.value?.setFormData?.(data)
|
|
|
|
|
+const clearFormData = () => previewRef.value?.clearFormData?.()
|
|
|
|
|
+const getUploadFields = () => previewRef.value?.getUploadFields?.()
|
|
|
|
|
+
|
|
|
|
|
+const handleReady = () => emit('ready')
|
|
|
|
|
+const handleSave = (data: Record<string, any>) => emit('save', data)
|
|
|
|
|
+
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
|
+ if (tempHost) {
|
|
|
|
|
+ document.body.removeChild(tempHost)
|
|
|
|
|
+ tempHost = null
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({
|
|
|
|
|
+ loadByTemplateId,
|
|
|
|
|
+ loadByRefId,
|
|
|
|
|
+ loadFromConfig,
|
|
|
|
|
+ getBoundFormData,
|
|
|
|
|
+ setFormData,
|
|
|
|
|
+ clearFormData,
|
|
|
|
|
+ getUploadFields,
|
|
|
|
|
+ // 原始 register 仍保留,供高级用法使用
|
|
|
|
|
+ register: (options: Parameters<InstanceType<typeof OptimizedTemplatePreview>['register']>[0]) =>
|
|
|
|
|
+ previewRef.value?.register(options)
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.ssjson-report-viewer {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ min-height: 400px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|