| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411 |
- <template>
- <div v-show="showSpread"
- :class="[!isFullscreen ? 'lab-designer-container' : 'lab-designer-container-fullscreen']"
- v-loading="loading" >
- <div class="header-row">
- <div class="title">
- <slot name="title"></slot>
- </div>
- <div class="unit-footer-btns">
- <el-dropdown trigger="click">
- <el-button type="primary" plain style="margin-right: 12px;">
- 更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
- </el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item @click="handleGenerate(true)">
- <div class="dropdown-item-content">
- <el-button type="warning" plain style="margin-right: 12px;">生成续页</el-button>
- </div>
- </el-dropdown-item>
- <el-dropdown-item @click="openWingdingsDialog">
- <div class="dropdown-item-content">
- <el-button type="info" plain style="margin-right: 12px;">插入特殊符号</el-button>
- </div>
- </el-dropdown-item>
- <el-dropdown-item @click="handleCopyRow">
- <div class="dropdown-item-content">
- <el-button type="primary" plain style="margin-right: 12px;">复制表单</el-button>
- </div>
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- <el-button type="success" @click="handleSave">保存</el-button>
- <BatchUploadFile @uploadImg="addPic" :colList="colListData" />
- <el-button type="primary" @click="openPdf">预览 PDF</el-button>
- <el-button type="danger" @click="close">关闭</el-button>
- </div>
- </div>
- <div v-loading="loading" ref="previewContainer" class="spread-container"></div>
- </div>
- <div v-show="showPdf" class="pdf-viewer-container" v-loading="pdfLoading" ref="pdfViewer">
- <div class="pdf-viewer-content">
- <VuePdfEmbed
- :key="pdfTimestamp"
- :height="pdfViewerHeight"
- :width="pdfContentWidth"
- :source="recordSource"
- text-layer
- :annotation-layer="false"
- @rendered="handlePdfRendered"
- />
- </div>
- </div>
-
- <!-- Wingdings 符号选择弹窗 -->
- <el-dialog
- v-model="wingdingsDialogVisible"
- title="选择特殊符号"
- width="600px"
- :close-on-click-modal="false"
- >
- <div class="wingdings-container">
- <div class="wingdings-grid">
- <div
- v-for="(item, index) in getWingdingsList"
- :key="index"
- class="wingdings-item"
- @click="selectWingdings(item)"
- >
- <span class="wingdings-symbol">{{ item.value }}</span>
- <span class="wingdings-label">{{ item.label }}</span>
- </div>
- </div>
- </div>
- <template #footer>
- <el-button @click="wingdingsDialogVisible = false">取消</el-button>
- </template>
- </el-dialog>
- </template>
- <script lang="ts" setup>
- import '@grapecity-software/spread-sheets-designer-resources-cn'
- import * as GC from '@grapecity-software/spread-sheets'
- // 导入PDF导出模块
- import '@grapecity-software/spread-sheets-pdf'
- import VuePdfEmbed from 'vue-pdf-embed'
- import 'vue-pdf-embed/dist/styles/annotationLayer.css'
- import 'vue-pdf-embed/dist/styles/textLayer.css'
- import * as ExcelIO from "@grapecity-software/spread-excelio"
- import SpreadDesigner from '@/components/SpreadDesigner/index.vue'
- import { Printer, Download, Refresh, DocumentAdd, Picture, ArrowDown } from '@element-plus/icons-vue'
- import { useTagsViewStore } from '@/store/modules/tagsView'
- import {
- getPDF
- } from '@/api/laboratory/standard/template';
- import {
- getPDFByInspectionLocal,
- getStandardTemplateInfo,
- } from '@/api/pressure2/standard/template'
- import {ref, onMounted,onUnmounted} from "vue";
- import {InitParams} from './SpreadInterface';
- import { buildFileUrl } from '@/utils'
- import axios from 'axios'
- import { DynamicTbApi } from '@/api/pressure2/dynamictb'
- import {DynamicTbColApi} from "@/api/pressure2/dynamictbcol";
- import { ExcelApi } from '@/api/pressure2/excel'
- import { DynamicTbValApi } from '@/api/pressure2/dynamictbval'
- import BatchUploadFile from "./BatchUploadFile.vue";
- import {editReport, handleCopy, collectSheetDataSource, collectShapesIntoDataSource, serializeDataSourceForStorage} from "@/utils/reportUtil";
- import Designer from '@grapecity-software/spread-sheets-designer-vue'
- import { ElLoading } from "element-plus"
- import {getPDF2, getPDFByInspection} from "@/api/pressure2/standard/template";
- import { useDictStore } from '@/store/modules/dict'
- defineOptions({ name: 'SpreadViewer' });
- const props = defineProps({
- initData: {
- type: Object as PropType<InitParams>,
- default: ()=>({}),
- required: true
- },
- isFullscreen:{
- type: Boolean,
- default: false,
- required: false
- }
- });
- const insId=ref<string>(null);
- const colListData=ref([]);
- const addPic = (picData) => {
- if (previewSpread) {
- if (props.initData.refName == '红外热成像检测') {
- picData.name.sheet = previewSpread.getActiveSheet().name()
- previewSpread.getActiveSheet().shapes.addPictureShape(JSON.stringify(picData.name), picData.base, picData.name.x, picData.name.y, 240, 320);
- return
- }
- picData.name.sheet = previewSpread.getActiveSheet().name()
- previewSpread.getActiveSheet().shapes.addPictureShape(JSON.stringify(picData.name), picData.base, picData.name.x, picData.name.y, picData.name.width, picData.name.height);
- }
- }
- /*
- watch(()=>[props.initData.templateId,props.initData.refId],([newTide,newRid],[oldTid,oldRid])=>{
- if(newTide && newRid){
- initPreview();
- }
- }); */
- //const route = useRoute()
- // spread相关
- const tagsViewStore = useTagsViewStore()
- const dictStore = useDictStore()
- const loading = ref(true)
- const showSpread=ref(true)
- const previewContainer = ref(null)
- let sheetData = {};
- let arrayFieldOffsetInfos = [];
- const designerKey = import.meta.env.VITE_SPREADJS_DESIGNER_KEY
- const nodeEnv = import.meta.env.VITE_NODE_ENV
- const licenseKey = import.meta.env.VITE_SPREADJS_LICENSE_KEY
- GC.Spread.Sheets.Designer.LicenseKey = designerKey
- //同时对SpreadJS与ExcelIO进行授权
- GC.Spread.Sheets.LicenseKey = licenseKey
- // pdf相关
- let previewSpread = null
- const pdfLoading = ref(false);
- const showPdf=ref(false);
- const recordSource = ref('')
- const pdfViewer = ref()
- const pdfContentWidth = ref(800)
- const pdfViewerHeight = ref(600)
- const pdfTimestamp = ref('')
- // Wingdings 弹窗相关
- const wingdingsDialogVisible = ref(false)
- const currentCell = ref(null)
- const getWingdingsList = computed(
- () => dictStore.getDictMap['Wingdings']
- )
- const handlePdfRendered = () => {
- pdfLoading.value = false
- }
- // 获取模版详情
- const fetchTemplateData = async () => {
- console.log(this)
- if(!props.initData.templateId){
- return;
- }
- const templateRes = await getStandardTemplateInfo({ id: props.initData.templateId })
- if (templateRes && templateRes.fileUrl) {
- const fileUrl = buildFileUrl(templateRes.fileUrl)
- const response = await axios.get(fileUrl, { responseType: 'blob' });
- return new Blob([response.data], { type: response.headers['content-type'] });
- }
- return;
- }
- const initPreview = async () => {
- const { refId, opType, templateId, enterPdfUrl, issueUrl } = props.initData;
-
- if (!refId) {
- return false;
- }
-
- // 有预生成的 PDF URL 时,跳过表格渲染直接预览 PDF
- const directPdfUrl = (enterPdfUrl || issueUrl || '').trim();
- if (opType === 1 && directPdfUrl) {
- loading.value = false;
- showSpread.value = false;
- showPdf.value = true;
- recordSource.value = buildFileUrl(directPdfUrl);
- pdfTimestamp.value = Date.now();
- console.log("使用url")
- setTimeout(() => {
- calculatePdfSize()
- }, 100)
- return;
- }
-
- loading.value = true;
-
- if (opType === 0) {
- showSpread.value = true;
- pdfLoading.value = false;
- }
- previewSpread?.destroy();
- previewSpread = new GC.Spread.Sheets.Workbook(previewContainer.value);
- const handleOpenError = (error: any) => {
- console.error('文件打开失败:', error);
- loading.value = false;
- };
- const handleCopyIfNeeded = () => {
- if (opType === 0) {
- handleCopy(previewSpread);
- }
- };
- const initCommonData = async () => {
- const res = await DynamicTbValApi.getDynamicTbInsAndValByRefId(props.initData);
- insId.value = res.dynamicTbInsRespVO.id;
- dynamicTbColRespVOList = res.dynamicTbColRespVOList;
- colListData.value = res.dynamicTbColRespVOList.filter(i => i.colValType === 4);
- return res;
- };
- if (nodeEnv === 'dev' || nodeEnv === 'hst' || nodeEnv === 'uat' || nodeEnv === 'prod') {
- try {
- // 1. 获取实例和字段数据(数据绑定留给前端处理,后端只做图片/高亮/续页处理)
- const res = await initCommonData();
- // 2. 调用后端接口获取处理好的 SJS
- const apiMethod = ExcelApi.excel;
- // const apiMethod = nodeEnv === 'dev' ? ExcelApi.process : ExcelApi.excel;
- const sjsBlob: any = await apiMethod({ templateId, refId });
- // 3. 打开 SJS 后在回调中绑定数据源
- previewSpread.open(sjsBlob, () => {
- console.log('后端处理完成,预览加载完成');
- const sheets = previewSpread.sheets;
- // 构建数据源并绑定(后端不 setDataSource,交由前端处理)
- // 后端已将 .jpg/.png 路径处理为背景图片,此处设 null 避免覆盖
- if (res.dynamicTbValRespVOList && res.dynamicTbValRespVOList.length > 0) {
- sheetData = {};
- res.dynamicTbValRespVOList.forEach(i => {
- const val = i.valValue;
- if (typeof val === 'string') {
- const trimmed = val.trim();
- // 后端已将图片路径值处理为背景图片,跳过避免文字覆盖
- if (trimmed.endsWith('.jpg') || trimmed.endsWith('.png')) {
- // sheetData[i.colCode] = null;
- } 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;
- }
- });
- }
- let dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource(sheetData);
- if (props.initData.dataSource) {
- dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource(props.initData.dataSource);
- }
- sheets.forEach(sheet => sheet.setDataSource(dataSource1));
- // 表格行样式同步(数据绑定后前端处理)
- handleTableRowsAfterDataBind(sheets);
- // 续页处理
- hiddenPage().then(() => {
- if (opType === 1) {
- showSpread.value = false;
- pdfLoading.value = true;
- openPdf();
- } else {
- showPdf.value = false;
- loading.value = false;
- handleCopyIfNeeded();
- }
- });
- }, handleOpenError);
- } catch (error) {
- console.error('后端处理 Excel 失败:', error);
- loading.value = false;
- }
- return;
- }
- try {
- const blob = await fetchTemplateData();
- if (!blob) return;
- previewSpread.open(blob, async () => {
- console.log('预览加载完成');
- const sheets = previewSpread.sheets;
-
- const res = await DynamicTbValApi.getDynamicTbInsAndValByRefId(props.initData);
- insId.value = res.dynamicTbInsRespVO.id;
- if (res.dynamicTbValRespVOList && res.dynamicTbValRespVOList.length > 0) {
- sheetData = {};
- res.dynamicTbValRespVOList.forEach(i => {
- const val = i.valValue;
- if (typeof val === 'string') {
- const trimmed = val.trim();
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
- 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;
- }
- });
- }
- let dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource(sheetData);
- if (props.initData.dataSource) {
- dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource(props.initData.dataSource);
- }
- sheets.forEach(sheet => sheet.setDataSource(dataSource1));
- dynamicTbColRespVOList = res.dynamicTbColRespVOList;
- const editCols = res.dynamicTbColRespVOList.filter(i => i.isEdit).map(i => i.colCode);
- const imgCols = res.dynamicTbColRespVOList.filter(i => i.colValType === 4).map(i => i.colCode);
- colListData.value = res.dynamicTbColRespVOList.filter(i => i.colValType === 4);
- for (const sheet of sheets) {
- await editReport(sheet, editCols, imgCols, opType);
- }
-
- handleTableRowsAfterDataBind(sheets);
- await hiddenPage();
- if (opType === 1) {
- showSpread.value = false;
- pdfLoading.value = true;
- await openPdf();
- } else {
- showPdf.value = false;
- loading.value = false;
- handleCopyIfNeeded();
- }
- }, handleOpenError);
- } catch (error) {
- console.error('预览加载失败:', error);
- }
- }
- let dynamicTbColRespVOList;
- const openPdf = async () => {
- const formData = new FormData()
- // 在异步操作开始前捕获 opType,防止切换项目时 props.initData 被覆盖导致竞态
- const currentOpType = props.initData.opType;
- const currentManualUrl = props.initData.manualUrl;
- const rules = []
- previewSpread.sheets.forEach((sheet) => {
- sheet.conditionalFormats.getRules().forEach(rule => {
- if (rule.condition() && rule.condition().formula() == 'TRUE') {
- console.log(rule)
- rules.push({
- sheet,
- rule
- })
- }
- })
- rules.forEach(rule => {
- sheet.conditionalFormats.removeRule(rule.rule)
- })
- });
- await previewSpread.save(async function (blob) {
- formData.append('file', blob)
- let response = null
- //prod环境葡萄城pdf接口
- if (nodeEnv == 'prod') {
- // 手动拼接pdf的url,适配检验方案
- if (currentManualUrl){
- formData.append('manualUrl', currentManualUrl)
- response = await getPDFByInspection(formData)
- }else{
- response = await getPDF2(formData)
- }
- } else {
- //本地测试环境葡萄城pdf接口
- if (currentManualUrl){
- // 手动拼接pdf的url,适配检验方案
- formData.append('manualUrl', currentManualUrl)
- response = await getPDFByInspectionLocal(formData)
- }else{
- response = await getPDF2(formData)
- }
- }
- if (response) {
- const flow = new Blob([response], { type: 'application/pdf' })
- recordSource.value = window.URL.createObjectURL(flow);
- if(currentOpType==0){
- loading.value = false;
- window.open(recordSource.value, '_blank');
- }
- if(currentOpType==1){
- //showSpread.value=false;
- pdfTimestamp.value = Date.now();
- showPdf.value=true;
- pdfLoading.value=true;
- setTimeout(() => {
- calculatePdfSize()
- },100)
- }
- }
- }, (e) => {
- }, {
- includeBindingSource: true
- })
- previewSpread.sheets.forEach((sheet) => {
- rules.forEach(rule => {
- if (rule.sheet === sheet){
- sheet.conditionalFormats.addRule(rule.rule)
- }
- })
- });
- }
- const handleSpreadPrint = () => {
- if (!previewSpread) return
- previewSpread.print()
- }
- /**
- * 打开 Wingdings 符号选择弹窗
- */
- const openWingdingsDialog = () => {
- if (!previewSpread) {
- ElMessage.warning('请先加载文档')
- return
- }
-
- // 获取当前选中的单元格
- const activeSheet = previewSpread.getActiveSheet()
- const selections = activeSheet.getSelections()
-
- if (!selections || selections.length === 0) {
- ElMessage.warning('请先选择一个单元格')
- return
- }
-
- // 保存当前单元格信息
- const selection = selections[0]
- currentCell.value = {
- sheet: activeSheet,
- row: selection.row,
- col: selection.col
- }
-
- wingdingsDialogVisible.value = true
- }
- /**
- * 选择 Wingdings 符号并插入到单元格
- */
- const selectWingdings = (item) => {
- if (!currentCell.value) {
- ElMessage.error('未找到目标单元格')
- return
- }
-
- const { sheet, row, col } = currentCell.value
- const cell = sheet.getCell(row, col)
- // if (!cell.allowEditInCell()){
- // wingdingsDialogVisible.value = false
- // return;
- // }
- // 获取当前单元格的值
- const currentValue = cell.value() || ''
-
- // 追加符号到单元格
- const newValue = currentValue + item.value
- cell.value(newValue)
-
- ElMessage.success(`已插入符号: ${item.label}`)
- wingdingsDialogVisible.value = false
- }
- const handleSave = () => {
- loading.value = true;
- let dataSource = {};
- const allSheetsData = []
- previewSpread.sheets.forEach((sheet) => {
- if (sheet.name() && sheet.name() !== '试用版警告') {
- const sheetData = collectSheetDataSource(sheet);
- const pageNo = getPageNoFromSheet(sheet);
- allSheetsData.push({ pageNo, data: sheetData, sheet });
- }
- });
- let copyRangePathSet = new Set();
- const hasContinuationPages = allSheetsData.some(({ pageNo }) => pageNo > 1);
- let pageNoBindingPath = null
- if (hasContinuationPages && generateData && generateData.copyRange) {
- const rangeInfo = getCopyRangeInfo();
- const templateSheet = previewSpread.getSheetFromName(generateData.sheetName);
- if (templateSheet && rangeInfo) {
- getCopyRangeBindingPaths(templateSheet, rangeInfo).forEach(p => copyRangePathSet.add(p));
- pageNoBindingPath = templateSheet.getBindingPath(rangeInfo.row, rangeInfo.col)
- }
- }
- for (const { pageNo, data, sheet } of allSheetsData) {
- for (const key in data) {
- if (pageNo > 1 && key === pageNoBindingPath) continue
- if (arrayFieldOffsetInfos.some(oi => key.startsWith(oi.prefix + '_'))) continue
- if (hasContinuationPages && copyRangePathSet.has(key)) {
- if (!dataSource[key]) {
- dataSource[key] = [];
- }
- if (Array.isArray(dataSource[key])) {
- while (dataSource[key].length < pageNo) {
- dataSource[key].push(null);
- }
- dataSource[key][pageNo - 1] = data[key];
- }
- } else if (hasContinuationPages && Array.isArray(sheetData?.[key]) && !arrayFieldOffsetInfos.some(oi => key === oi.prefix)) {
- if (!dataSource[key]) {
- dataSource[key] = [];
- }
- if (Array.isArray(data[key])) {
- dataSource[key] = dataSource[key].concat(data[key]);
- }
- } else {
- if (!dataSource.hasOwnProperty(key) && data[key] != null) {
- dataSource[key] = data[key];
- }
- }
- }
- collectShapesIntoDataSource(sheet, dataSource);
- }
- for (const oi of arrayFieldOffsetInfos) {
- const { prefix, fields } = oi
- const copyMinIndex = Math.min(...fields.map(f => f.index))
- const copyFieldNames = new Set(fields.map(f => f.path))
- const result = []
- const templateSheetData = allSheetsData.find(s => s.sheet.name() === generateData.sheetName)
- if (templateSheetData) {
- for (let i = 1; i < copyMinIndex; i++) {
- const fieldName = `${prefix}_${i}`
- if (!copyFieldNames.has(fieldName)) {
- const val = templateSheetData.data[fieldName]
- if (val != null) {
- result.push(Array.isArray(val) ? val[0] || null : val)
- } else {
- result.push(null)
- }
- }
- }
- }
- for (const { data, sheet } of allSheetsData) {
- if (sheet.name() !== generateData.sheetName && !sheet.name().startsWith(generateData.sheetName + ' (')) continue
- for (const f of fields) {
- result.push(data[f.path] != null ? data[f.path] : null)
- }
- }
- dataSource[`${prefix}_1`] = result
- }
- if (Object.keys(dataSource).length > 0) {
- const serializedData = serializeDataSourceForStorage(dataSource);
- console.log(serializedData);
- DynamicTbValApi.saveAllColValue(insId.value, serializedData).then(res => {
- if (res) {
- ElMessage.success('保存成功')
- emit('saveSuccess', {insId:insId.value,dataSource:serializedData,refId:props.initData.refId});
- } else{
- ElMessage.error('保存失败')
- emit('saveFail');
- }
- }).catch(e => {
- ElMessage.error('保存失败')
- emit('saveFail');
- }).finally(() => {
- loading.value = false;
- });
- } else {
- loading.value = false;
- }
- }
- const distributePageDataSources = () => {
- if (!generateData || !generateData.copyRange) return
- previewSpread.sheets.forEach((sheet) => {
- if (sheet.name() && sheet.name() !== '试用版警告' && sheet.name() !== generateData.sheetName) {
- const pageNo = getPageNoFromSheet(sheet)
- if (pageNo > 1 && sheetData && Object.keys(sheetData).length > 0) {
- const pageData = buildPageDataSource(sheetData, pageNo)
- const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(pageData)
- sheet.setDataSource(dataSource)
- }
- }
- })
- }
- const handleCopyRow = () => {
- if (!previewSpread) return ElMessage.warning('请先加载文档')
- const activeSheet = previewSpread.getActiveSheet()
- const selections = activeSheet.getSelections()
- if (!selections || selections.length === 0) return ElMessage.warning('请先选择一个单元格')
- const { row, col } = selections[0]
- // 找到包含当前单元格的 table
- const tables = activeSheet.tables?.all() || []
- let targetTable = null
- for (const table of tables) {
- const range = table.range()
- if (row >= range.row && row < range.row + range.rowCount && col >= range.col && col < range.col + range.colCount) {
- targetTable = table
- break
- }
- }
- if (!targetTable) return ElMessage.warning('当前单元格不在表格内,请选中表格内的单元格')
- const range = targetTable.range()
- if (row < range.row + 1) return ElMessage.warning('请选中表格内的数据行')
- // 绑定源中追加空行,让 table 自动扩展一行
- const dataSource = activeSheet.getDataSource()
- const sourceData = dataSource?.getSource()
- const bindingPath = targetTable.bindingPath()
- if (!bindingPath || !sourceData?.[bindingPath] || !Array.isArray(sourceData[bindingPath])) {
- return ElMessage.warning('表格未绑定数据源')
- }
- // 在刷新数据源前,先收集第一行的合并规则(刷新时会清空)
- const { col: tableCol, colCount: tableColCount } = range
- const firstRowSpans = collectFirstRowSpans(activeSheet, range.row + 1, tableCol, tableColCount)
- // 记录新行位置(当前 rowCount 就是新增行的索引偏移)
- const newRowIndex = range.row + range.rowCount
- // 在绑定数组中追加空对象
- sourceData[bindingPath].push({})
- activeSheet.setDataSource(null)
- activeSheet.setDataSource(dataSource)
- // 复制样式/行高/合并到新行
- previewSpread.suspendPaint()
- try {
- const copyOps = GC.Spread.Sheets.CopyToOptions.style | GC.Spread.Sheets.CopyToOptions.formula
- activeSheet.copyTo(row, tableCol, newRowIndex, tableCol, 1, tableColCount, copyOps)
- activeSheet.setRowHeight(newRowIndex, activeSheet.getRowHeight(row))
- if (firstRowSpans.length > 0) {
- applyMergeRules(activeSheet, newRowIndex, firstRowSpans)
- }
- } finally {
- previewSpread.resumePaint()
- }
- ElMessage.success('已复制行')
- }
- const close = () => {
- emit('close')
- }
- const getPageNoFromSheet = (sheet) => {
- const tag = sheet.tag()
- if (tag !== undefined && tag !== null && tag !== '' && !isNaN(parseInt(tag))) {
- return parseInt(tag)
- }
- const name = sheet.name()
- const match = name.match(/\((\d+)\)\s*$/)
- if (match) {
- return parseInt(match[1])
- }
- return 1
- }
- const getCopyRangeInfo = () => {
- if (!generateData || !generateData.copyRange) return null
- const col = parseInt(generateData.copyRange.topLeft.split(',')[0])
- const row = parseInt(generateData.copyRange.topLeft.split(',')[1])
- const colCount = parseInt(generateData.copyRange.topRight.split(',')[0]) - col
- const rowCount = parseInt(generateData.copyRange.bottomLeft.split(',')[1]) - row
- return { col, row, colCount, rowCount }
- }
- const getCopyRangeBindingPaths = (sheet, rangeInfo) => {
- if (!sheet || !rangeInfo) return []
- const { col, row, colCount, rowCount } = rangeInfo
- const paths = []
- for (let i = 0; i <= colCount; i++) {
- for (let j = 0; j <= rowCount; j++) {
- const bindingPath = sheet.getBindingPath(row + j, col + i)
- if (bindingPath) {
- paths.push(bindingPath)
- }
- }
- }
- return paths
- }
- const buildPageDataSource = (baseData, pageNo) => {
- if (!generateData || !generateData.copyRange) return baseData
- const rangeInfo = getCopyRangeInfo()
- if (!rangeInfo) return baseData
- const sheet = previewSpread.getSheetFromName(generateData.sheetName)
- if (!sheet) return baseData
- const pageData = { ...baseData }
- const bindingPaths = getCopyRangeBindingPaths(sheet, rangeInfo)
- const bindingPathSet = new Set(bindingPaths)
- for (const path of bindingPaths) {
- const val = baseData[path]
- if (Array.isArray(val)) {
- pageData[path] = val[pageNo - 1] !== undefined ? val[pageNo - 1] : null
- } else if (pageNo > 1) {
- pageData[path] = null
- }
- }
- const rowsPerPage = rangeInfo.rowCount + 1
- for (const key in baseData) {
- if (bindingPathSet.has(key)) continue
- const val = baseData[key]
- if (Array.isArray(val)) {
- const start = (pageNo - 1) * rowsPerPage
- pageData[key] = val.slice(start, start + rowsPerPage)
- }
- }
- for (const oi of arrayFieldOffsetInfos) {
- const { fullArray, fields } = oi
- const copyMinIndex = Math.min(...fields.map(f => f.index))
- const start = (copyMinIndex - 1) + (pageNo - 1) * oi.rowsPerPage
- let firstIdx = -1, lastIdx = -1
- let firstVal = undefined
- for (const f of fields) {
- const idx = start + (f.index - copyMinIndex)
- if (firstIdx === -1) { firstIdx = idx; firstVal = idx < fullArray.length ? fullArray[idx] : null }
- lastIdx = idx
- pageData[f.path] = idx < fullArray.length ? fullArray[idx] : null
- }
- console.log(`[buildPageDataSource] pageNo=${pageNo}, prefix=${oi.prefix}, start=${start}, idxRange=[${firstIdx},${lastIdx}], firstIdxVal=${JSON.stringify(firstVal)}, fullArrayLen=${fullArray.length}, copyMinIndex=${copyMinIndex}, rowsPerPage=${oi.rowsPerPage}`)
- }
- return pageData
- }
- const distributeArrayFieldToCells = () => {
- if (!generateData || !generateData.copyRange) {
- console.log('[distributeArrayFieldToCells] 跳过: generateData/copyRange 为空')
- return
- }
- if (!sheetData || !Object.keys(sheetData).length) {
- console.log('[distributeArrayFieldToCells] 跳过: sheetData 为空')
- return
- }
- const rangeInfo = getCopyRangeInfo()
- if (!rangeInfo) {
- console.log('[distributeArrayFieldToCells] 跳过: rangeInfo 为空')
- return
- }
- const baseSheet = previewSpread.getSheetFromName(generateData.sheetName)
- if (!baseSheet) {
- console.log('[distributeArrayFieldToCells] 跳过: 找不到基础 sheet')
- return
- }
- const pattern = /^(.+)_(\d+)$/
- const prefixGroups = {}
- for (let r = 0; r <= rangeInfo.rowCount; r++) {
- for (let c = 0; c <= rangeInfo.colCount; c++) {
- const bp = baseSheet.getBindingPath(rangeInfo.row + r, rangeInfo.col + c)
- if (bp) {
- const match = bp.match(pattern)
- if (match) {
- const pfx = match[1]
- if (!prefixGroups[pfx]) prefixGroups[pfx] = []
- prefixGroups[pfx].push({
- path: bp, prefix: pfx, index: parseInt(match[2]), rowInRange: r, col: c
- })
- }
- }
- }
- }
- console.log('[distributeArrayFieldToCells] copyRange 扫描到的前缀组:', Object.keys(prefixGroups))
- arrayFieldOffsetInfos = []
- const prefixSet = new Set()
- for (const pfx of Object.keys(prefixGroups)) {
- const fields = prefixGroups[pfx]
- let fullArray = null
- for (let i = 0; i <= 1000; i++) {
- const candidate = `${pfx}_${i}`
- const val = sheetData[candidate]
- if (Array.isArray(val) && val.length > 0) {
- fullArray = val
- break
- }
- }
- if (!fullArray) {
- console.log(`[distributeArrayFieldToCells] 前缀 "${pfx}" 未找到数组字段`)
- continue
- }
- for (let i = 0; i < fullArray.length; i++) {
- const key = `${pfx}_${i + 1}`
- sheetData[key] = fullArray[i]
- }
- arrayFieldOffsetInfos.push({ prefix: pfx, fullArray, rowsPerPage: rangeInfo.rowCount + 1, fields })
- prefixSet.add(pfx)
- console.log(`[distributeArrayFieldToCells] 前缀 "${pfx}" 成功: 数组长度=${fullArray.length}, rowsPerPage=${rangeInfo.rowCount}, copyRange 字段数=${fields.length}`)
- }
- if (arrayFieldOffsetInfos.length === 0) {
- console.log('[distributeArrayFieldToCells] 无有效数组,结束')
- return
- }
- previewSpread.suspendPaint()
- try {
- const copyStartRow = rangeInfo.row
- const copyRangeRowSet = new Set()
- for (let r = 0; r < rangeInfo.rowCount; r++) {
- copyRangeRowSet.add(copyStartRow + r)
- }
- for (const oi of arrayFieldOffsetInfos) {
- for (const f of oi.fields) {
- const idx = f.index - 1
- const cellRow = copyStartRow + f.rowInRange
- if (idx < oi.fullArray.length) {
- baseSheet.setValue(cellRow, rangeInfo.col + f.col, oi.fullArray[idx])
- }
- }
- }
- for (let r = 0; r < baseSheet.getRowCount(); r++) {
- if (copyRangeRowSet.has(r)) continue
- for (let c = 0; c < baseSheet.getColumnCount(); c++) {
- const bp = baseSheet.getBindingPath(r, c)
- if (bp) {
- const match = bp.match(pattern)
- if (match && prefixSet.has(match[1])) {
- const idx = parseInt(match[2]) - 1
- const oi = arrayFieldOffsetInfos.find(o => o.prefix === match[1])
- if (oi && idx < oi.fullArray.length) {
- baseSheet.setValue(r, c, oi.fullArray[idx])
- }
- }
- }
- }
- }
- const baseDS = baseSheet.getDataSource()
- if (baseDS && baseDS.rT) {
- for (const oi of arrayFieldOffsetInfos) {
- oi.fullArray.forEach((val, idx) => {
- baseDS.rT[`${oi.prefix}_${idx + 1}`] = val
- })
- }
- }
- } finally {
- previewSpread.resumePaint()
- }
- }
- const adjustBaseSheetArrays = () => {
- if (!generateData || !generateData.copyRange || !sheetData || !Object.keys(sheetData).length) return
- const rangeInfo = getCopyRangeInfo()
- if (!rangeInfo) return
- const baseSheet = previewSpread.getSheetFromName(generateData.sheetName)
- if (!baseSheet) return
- const bindingPaths = getCopyRangeBindingPaths(baseSheet, rangeInfo)
- const bindingPathSet = new Set(bindingPaths)
- const rowsPerPage = rangeInfo.rowCount + 1
- previewSpread.suspendPaint()
- try {
- for (let row = 0; row < baseSheet.getRowCount(); row++) {
- for (let col = 0; col < baseSheet.getColumnCount(); col++) {
- const bp = baseSheet.getBindingPath(row, col)
- if (bp && !bindingPathSet.has(bp) && Array.isArray(sheetData[bp])) {
- const val = baseSheet.getValue(row, col)
- if (Array.isArray(val)) {
- baseSheet.setValue(row, col, val.slice(0, rowsPerPage))
- }
- }
- }
- }
- } finally {
- previewSpread.resumePaint()
- }
- }
- const setPageNoToSheet = (sheet, pageNo, rangeInfo) => {
- if (!rangeInfo) return
- sheet.tag(String(pageNo))
- }
- let generateData
- let sheetNum = ref(2)
- /**
- * 生成续页
- * @param {boolean} isSetTimeout 是否设置定时器
- */
- const handleGenerate = async (isSetTimeout) => {
- if (!generateData || !generateData.sheetName){
- ElMessage.error('请配置模板续页名称')
- return
- }
- /**
- * 生成续页
- * @param isAdd 是否继续生成
- */
- const generate = async (isAdd) => {
- const sheet = previewSpread.getSheetFromName(generateData.sheetName)
- if (!sheet.visible()) {
- sheet.visible(true)
- }
- console.log('生成续页',generateData.sheetName + " (" + sheetNum.value + ")");
-
- previewSpread.commandManager().execute({
- cmd: "copySheet",
- sheetName: generateData.sheetName,
- targetIndex: 999,
- newName: generateData.sheetName + " (" + sheetNum.value + ")",
- includeBindingSource: true
- });
- // 范围内的字段叠加
- let col = parseInt(generateData.copyRange.topLeft.split(',')[0])
- let row = parseInt(generateData.copyRange.topLeft.split(',')[1])
- let colCount = parseInt(generateData.copyRange.topRight.split(',')[0]) - col
- let rowCount = parseInt(generateData.copyRange.bottomLeft.split(',')[1]) - row
- const sheet2 = previewSpread.getSheetFromName(generateData.sheetName + " (" + sheetNum.value + ")")
- const rangeInfo = getCopyRangeInfo()
- const pageNo = sheetNum.value
- sheetNum.value = sheetNum.value + 1
- // 先设页码(tag 持久化),后续 dataSource 会覆盖左上角单元格为正确数据
- setPageNoToSheet(sheet2, pageNo, rangeInfo)
- // 解绑 copySheet 带来的旧数据源,再绑定新数据源,防止旧数据覆盖新值
- if (sheet2) {
- sheet2.setDataSource(null)
- }
- if (sheet2 && sheetData && Object.keys(sheetData).length > 0) {
- const pageData = buildPageDataSource(sheetData, pageNo)
- const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(pageData)
- sheet2.setDataSource(dataSource)
- // 验证:读取续页 copyRange 第一行和最后一行实际单元格值
- if (rangeInfo) {
- const firstRowVals = []
- const lastRowVals = []
- for (let c = 0; c <= rangeInfo.colCount; c++) {
- const bp = sheet2.getBindingPath(rangeInfo.row, rangeInfo.col + c)
- const firstVal = sheet2.getValue(rangeInfo.row, rangeInfo.col + c)
- const lastVal = sheet2.getValue(rangeInfo.row + rangeInfo.rowCount, rangeInfo.col + c)
- firstRowVals.push(`${bp || '?'}=${JSON.stringify(firstVal)}`)
- lastRowVals.push(`${bp || '?'}=${JSON.stringify(lastVal)}`)
- }
- console.log(`[generate] pageNo=${pageNo} 续页第一行:`, firstRowVals.join(' | '))
- console.log(`[generate] pageNo=${pageNo} 续页最后行:`, lastRowVals.join(' | '))
- }
- }
- if (isAdd){
- let col = parseInt(generateData.copyRange.topLeft.split(',')[0])
- let row = parseInt(generateData.copyRange.topLeft.split(',')[1])
- let colCount = parseInt(generateData.copyRange.topRight.split(',')[0]) - col
- let rowCount = parseInt(generateData.copyRange.bottomLeft.split(',')[1]) - row
- if (!sheet2) {
- return;
- }
- if (generateData.hidden) {
- let isEmpty = true;
- for (let i = 0; i <= colCount; i++) {
- if (i === 0) continue
- const range = sheet2.getCell(row, col + i)
- if (range.value()) {
- isEmpty = false;
- break;
- }
- }
- if (isEmpty) {
- sheet2.visible(false)
- }
- }
- let isNotEmpty = false;
- for (let i = 0; i <= colCount; i++) {
- const range = sheet2.getCell(row + rowCount, col + i)
- if (range.value()) {
- isNotEmpty = true;
- break;
- }
- }
- console.log(`[generate] pageNo=${pageNo} 单元格检测 isNotEmpty=${isNotEmpty} (检查最后行 row=${row + rowCount}, 列范围=[${col},${col + colCount}])`)
- if (!isNotEmpty && arrayFieldOffsetInfos.length > 0) {
- for (const oi of arrayFieldOffsetInfos) {
- const copyMinIndex = Math.min(...oi.fields.map(f => f.index))
- const usedSoFar = (copyMinIndex - 1) + pageNo * oi.rowsPerPage
- console.log(`[generate] 续页检查 pageNo=${pageNo}, prefix=${oi.prefix}, usedSoFar=${usedSoFar}, fullArrayLen=${oi.fullArray.length}, 是否需续页=${oi.fullArray.length > usedSoFar}`)
- if (oi.fullArray.length > usedSoFar) {
- isNotEmpty = true
- break
- }
- }
- }
- if (isNotEmpty) {
- await generate(true)
- }
- }
- }
- if (isSetTimeout) {
- const loading = ElLoading.service({text: '正在生成中'})
- setTimeout(async () => {
- try {
- await generate(false)
- } finally {
- loading.close()
- }
- }, 20)
- } else {
- const loading = ElLoading.service({text: '生成续页中,数据量较大'})
- await generate(true)
- loading.close()
- }
- }
- /**
- * 隐藏续页
- */
- const hiddenPage = async () => {
- sheetNum.value = 2
- const data = await DynamicTbApi.getDynamicTb(props.initData.templateId)
- if (data && data.copyConfig) {
- generateData = data.copyConfig
- if (!generateData.isContinuePage){
- return
- }
- } else {
- return
- }
- // 计算坐标
- let col = parseInt(generateData.copyRange.topLeft.split(',')[0])
- let row = parseInt(generateData.copyRange.topLeft.split(',')[1])
- let colCount = parseInt(generateData.copyRange.topRight.split(',')[0]) - col
- let rowCount = parseInt(generateData.copyRange.bottomLeft.split(',')[1]) - row
- distributeArrayFieldToCells()
- const sheet = previewSpread.getSheetFromName(generateData.sheetName)
- if (!sheet) {
- return;
- }
- if (generateData.hidden) {
- let isEmpty = true;
- for (let i = 0; i <= colCount; i++) {
- if (i === 0) continue
- const range = sheet.getCell(row, col + i)
- if (range.value()) {
- isEmpty = false;
- break;
- }
- }
- if (isEmpty) {
- sheet.visible(false)
- }
- }
- let isNotEmpty = false;
- for (let i = 0; i <= colCount; i++) {
- const range = sheet.getCell(row + rowCount, col + i)
- if (range.value()) {
- isNotEmpty = true;
- break;
- }
- }
- console.log('[hiddenPage] 单元格检测 isNotEmpty:', isNotEmpty, '| arrayFieldOffsetInfos:', arrayFieldOffsetInfos.length)
- if (arrayFieldOffsetInfos.length > 0) {
- for (const oi of arrayFieldOffsetInfos) {
- const copyMinIndex = Math.min(...oi.fields.map(f => f.index))
- const usedOnPage1 = (copyMinIndex - 1) + oi.rowsPerPage
- console.log(`[hiddenPage] 前缀=${oi.prefix}, 数组长度=${oi.fullArray.length}, copyMinIndex=${copyMinIndex}, rowsPerPage=${oi.rowsPerPage}, usedOnPage1=${usedOnPage1}`)
- if (oi.fullArray.length > usedOnPage1) {
- isNotEmpty = true
- break
- }
- }
- }
- console.log('[hiddenPage] 最终 isNotEmpty:', isNotEmpty)
- if (isNotEmpty) {
- await handleGenerate(false)
- adjustBaseSheetArrays()
- } else {
- distributePageDataSources()
- }
- }
- /**
- * 获取数据
- */
- const getData = () => {
- let dataSource = {};
- previewSpread.sheets.forEach((sheet) => {
- const sheetData = collectSheetDataSource(sheet);
- for (const key in sheetData) {
- if (dataSource[key] && sheetData[key] == sheetData[key]) {
- } else {
- dataSource[key] = sheetData[key];
- }
- }
- collectShapesIntoDataSource(sheet, dataSource);
- });
- return dataSource;
- }
- /**
- * 数据绑定后同步表格样式——将第一行(模板行)的合并规则、样式、行高复制到所有自动扩展的数据行
- * 参考 SpreadDesigner 的 handleSheetTableCopyToOptimized
- */
- const copyOptions = GC.Spread.Sheets.CopyToOptions.style | GC.Spread.Sheets.CopyToOptions.formula
- const handleTableRowsAfterDataBind = (sheets) => {
- previewSpread.suspendPaint()
- try {
- 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)
- // 表格内所有单元格设为可编辑
- sheet.getRange(row, col, rowCount, colCount).allowEditInCell(true)
- sheet.getRange(row, col, rowCount, colCount).locked(false)
- // 收集第一行的合并规则
- const firstRowSpans = collectFirstRowSpans(sheet, row, col, colCount)
- // 复制样式+行高 并 应用合并
- const firstRowHeight = sheet.getRowHeight(row)
- for (let r = 1; r < rowCount; r++) {
- const currentRow = row + r
- // 复制第一行样式和公式到当前数据行
- sheet.copyTo(row, col, currentRow, col, 1, colCount, copyOptions)
- // 复制行高
- sheet.setRowHeight(currentRow, firstRowHeight)
- // 应用合并规则
- if (firstRowSpans.length > 0) {
- applyMergeRules(sheet, currentRow, firstRowSpans)
- }
- }
- }
- }
- } finally {
- previewSpread.resumePaint()
- }
- }
- /** 收集第一行(模板行)的合并单元格规则 */
- const collectFirstRowSpans = (sheet, row, col, colCount) => {
- const firstRowSpans = []
- for (let c = col; c < col + colCount; c++) {
- const span = sheet.getSpan(row, c)
- if (span && span.row === row) {
- firstRowSpans.push({ startCol: span.col, colCount: span.colCount })
- c += span.colCount - 1
- }
- }
- // 如果没有预置合并单元格,根据第一行内容自动识别(相邻同值列合并)
- if (firstRowSpans.length === 0) {
- const firstRowValues = []
- for (let c = col; c < col + colCount; c++) {
- firstRowValues.push(sheet.getValue(row, c))
- }
- let startMergeCol = 0
- for (let c = 1; c <= colCount; c++) {
- if (c === colCount || firstRowValues[c] !== firstRowValues[c - 1]) {
- if (c - startMergeCol > 1) {
- const startCol = col + startMergeCol
- const mergeColCount = c - startMergeCol
- firstRowSpans.push({ startCol, colCount: mergeColCount })
- sheet.addSpan(row, startCol, 1, mergeColCount)
- }
- startMergeCol = c
- }
- }
- }
- return firstRowSpans
- }
- /** 将合并规则应用到指定行(直接按模板列的 span 结构合并,不做内容相等校验) */
- const applyMergeRules = (sheet, currentRow, firstRowSpans) => {
- firstRowSpans.forEach((mergeInfo) => {
- try {
- sheet.addSpan(currentRow, mergeInfo.startCol, 1, mergeInfo.colCount)
- } catch (error) {
- // 忽略合并失败
- }
- })
- }
- defineExpose({reloadView:initPreview, handleSave, getWorkbook: () => previewSpread,getData});
- const emit = defineEmits(['saveSuccess','docReady','docCreate','saveFail','close'])
- // 动态计算PDF查看器尺寸
- const calculatePdfSize = () => {
- pdfContentWidth.value = pdfViewer.value.clientWidth - 45
- pdfViewerHeight.value = pdfViewer.value.clientHeight
- console.log('props.pdfUrl', pdfContentWidth.value, pdfViewerHeight.value)
- }
- onMounted(() => {
- window.addEventListener('resize', calculatePdfSize)
- console.log("SpreadViewer,onMounted:"+JSON.stringify(props.initData));
- //initPreview();
- })
- onUnmounted(()=>{
- window.removeEventListener('resize', calculatePdfSize)
- previewSpread?.destroy();
- })
- </script>
- <style lang="scss" scoped>
- :deep(.default-toolbar) {
- padding-top: 0;
- }
- .lab-designer-container {
- position: relative;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- /*height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 140px);*/
- height: calc(100vh - var(--top-tool-height) - 5px );
- .spread-designer-container {
- width: calc(100% - 440px);
- height: 100%;
- padding: 0;
- }
- }
- .lab-designer-container-fullscreen {
- position: relative;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- /*height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 140px);*/
- height: 100%;
- .spread-designer-container {
- width: calc(100% - 440px);
- height: 100%;
- padding: 0;
- }
- }
- .header-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0px 20px 16px 20px;
- background: #fff;
- border-bottom: 1px solid #e8e8e8;
- }
- .header-row .title {
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- }
- .unit-footer-btns {
- align-items: center;
- }
- .dropdown-item-content {
- display: flex;
- align-items: center;
- gap: 8px;
- justify-content: center;
- width: 100%;
- }
- .spread-container {
- flex: 1;
- background: #f5f7fa;
- overflow: auto;
- padding: 0;
- }
- .pdf-viewer-container {
- width: 100%;
- height: calc(100% - 40px);
- //background-color: #8E8E9D;
- padding: 5px;
- box-sizing: border-box;
- overflow-y: auto;
- }
- .pdf-viewer-content {
- display: flex;
- justify-content: center;
- align-items: center;
- }
- // Wingdings 符号选择样式
- .wingdings-container {
- max-height: 500px;
- overflow-y: auto;
- }
- .wingdings-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
- gap: 12px;
- padding: 10px;
- }
- .wingdings-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 12px 8px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s;
-
- &:hover {
- border-color: #409eff;
- background-color: #ecf5ff;
- transform: translateY(-2px);
- box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
- }
- }
- .wingdings-symbol {
- font-size: 24px;
- margin-bottom: 4px;
- color: #303133;
- }
- .wingdings-label {
- font-size: 12px;
- color: #909399;
- text-align: center;
- word-break: break-all;
- }
- </style>
|