|
@@ -0,0 +1,656 @@
|
|
|
+// ppt发布逻辑
|
|
|
+import {apiSheetInfoByCode} from '@/api/sheet.js'
|
|
|
+import {apiPPTPublish} from '@/api/ppt.js'
|
|
|
+import { showToast,showLoadingToast } from 'vant';
|
|
|
+import pptxgen from "pptxgenjs";
|
|
|
+import { parse } from "himalaya";
|
|
|
+import _ from 'lodash'
|
|
|
+import {pptLayout,pptSlideMaster,pptSlideMasterEn,modelConfig} from '../utils/config'
|
|
|
+import {useUploadFileToOSS} from '@/hooks/useUploadFileToOSS'
|
|
|
+import moment from 'moment'
|
|
|
+
|
|
|
+let LoadingINS=null
|
|
|
+let PPTContentList=[]
|
|
|
+let pptId=0
|
|
|
+
|
|
|
+
|
|
|
+// 校验ppt内容是否完整
|
|
|
+function checkPPT(){
|
|
|
+ for (let index = 1; index < PPTContentList.length-1; index++) {
|
|
|
+ const element = PPTContentList[index];
|
|
|
+ if(!element.title){
|
|
|
+ console.log(`第${index}页内容不完整,请在pc端重新编辑!`);
|
|
|
+ showToast(`第${index}页内容不完整,请在pc端重新编辑!`);
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if(!element.elements.length===0){
|
|
|
+ showToast(`第${index}页内容不完整,请在pc端重新编辑!`);
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * PPT默认配置
|
|
|
+ * @param pptx ppt插件实例
|
|
|
+ * @param LayoutType ppt版式 1-10:7,2-16:9,3-4:3
|
|
|
+ * @param lang
|
|
|
+ */
|
|
|
+function PPTInit(pptx,LayoutType,lang='ch'){
|
|
|
+ let layout = pptLayout
|
|
|
+ let sliderMaster = lang==='ch'?pptSlideMaster:pptSlideMasterEn
|
|
|
+ if(LayoutType!==1){
|
|
|
+ layout = { name: "myppt", width: 10, height: LayoutType===2?5.625:7.5 }
|
|
|
+ const y = lang==='ch'?0:-0.1
|
|
|
+ const h = LayoutType===2?5.625:7.5
|
|
|
+ sliderMaster.objects[1] = {image: {x:0,y:y,w:10,h:lang==='ch'?h:h+0.1,path: lang==='ch'?"/pptImg/pptitem_bg.png":"/pptImg/pptitem_bg.png"}}
|
|
|
+ sliderMaster.slideNumber = {x:'95%',y:LayoutType===2?'92%':'95%',fontSize:12}
|
|
|
+ }
|
|
|
+ pptx.defineLayout(layout)
|
|
|
+ pptx.layout = layout.name
|
|
|
+ pptx.defineSlideMaster(sliderMaster)
|
|
|
+ return pptx
|
|
|
+}
|
|
|
+
|
|
|
+// svg转base64图片,参考:https://github.com/scriptex/svg64
|
|
|
+const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
|
|
|
+const PREFIX = 'data:image/svg+xml;base64,'
|
|
|
+const utf8Encode = (string) => {
|
|
|
+ string = string.replace(/\r\n/g, '\n')
|
|
|
+ let utftext = ''
|
|
|
+ for (let n = 0; n < string.length; n++) {
|
|
|
+ const c = string.charCodeAt(n)
|
|
|
+ if (c < 128) {
|
|
|
+ utftext += String.fromCharCode(c)
|
|
|
+ }
|
|
|
+ else if (c > 127 && c < 2048) {
|
|
|
+ utftext += String.fromCharCode((c >> 6) | 192)
|
|
|
+ utftext += String.fromCharCode((c & 63) | 128)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ utftext += String.fromCharCode((c >> 12) | 224)
|
|
|
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128)
|
|
|
+ utftext += String.fromCharCode((c & 63) | 128)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return utftext
|
|
|
+}
|
|
|
+const encode = (input) => {
|
|
|
+ let output = ''
|
|
|
+ let chr1, chr2, chr3, enc1, enc2, enc3, enc4
|
|
|
+ let i = 0
|
|
|
+ input = utf8Encode(input)
|
|
|
+ while (i < input.length) {
|
|
|
+ chr1 = input.charCodeAt(i++)
|
|
|
+ chr2 = input.charCodeAt(i++)
|
|
|
+ chr3 = input.charCodeAt(i++)
|
|
|
+ enc1 = chr1 >> 2
|
|
|
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
|
|
|
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
|
|
|
+ enc4 = chr3 & 63
|
|
|
+ if (isNaN(chr2)) enc3 = enc4 = 64
|
|
|
+ else if (isNaN(chr3)) enc4 = 64
|
|
|
+ output = output + characters.charAt(enc1) + characters.charAt(enc2) + characters.charAt(enc3) + characters.charAt(enc4)
|
|
|
+ }
|
|
|
+ return output
|
|
|
+}
|
|
|
+function svg2Base64 (element) {
|
|
|
+ const XMLS = new XMLSerializer()
|
|
|
+ const svg = XMLS.serializeToString(element)
|
|
|
+ return PREFIX + encode(svg)
|
|
|
+ //return PREFIX + new http.Base64().encode(svg)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取图表图片
|
|
|
+ * @param id 图表dom中的id
|
|
|
+ */
|
|
|
+function changeUrl(id){
|
|
|
+ console.log(id);
|
|
|
+ let img = new Image()
|
|
|
+ // const svgHtml = $(`#${id} svg`)[0].outerHTML
|
|
|
+ /* img.src = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgHtml)))}` */
|
|
|
+ img.src = svg2Base64($(`#${id} svg`)[0]);//不支持unescape时可用
|
|
|
+ const imgW = $(`#${id}`)[0].offsetWidth*4,imgH = $(`#${id}`)[0].offsetHeight*4
|
|
|
+ let canvas = document.createElement('canvas')
|
|
|
+ canvas.width = imgW
|
|
|
+ canvas.height = imgH
|
|
|
+ return new Promise(resolve=>{
|
|
|
+ img.onload = ()=>{
|
|
|
+ //console.log('src',img.src)
|
|
|
+ let ctx = canvas.getContext("2d")
|
|
|
+ ctx.drawImage(img, 0, 0, imgW, imgH)
|
|
|
+ resolve(canvas.toDataURL("image/png"))
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+//解析富文本,参考https://github.com/pipipi-pikachu/PPTist/tree/master/src/utils/htmlParser
|
|
|
+//将text中被转义的字符转义回来,如:a<b 在富文本内会被转义成 a<b
|
|
|
+function replacer (_, p1){
|
|
|
+ return {
|
|
|
+ "<": "<",
|
|
|
+ ">": ">",
|
|
|
+ "“": "“",
|
|
|
+ "”": "”",
|
|
|
+ "&":"&",
|
|
|
+ "‘":"‘",
|
|
|
+ "’":"’",
|
|
|
+ "—":'——',
|
|
|
+ "≥":'≥',
|
|
|
+ "≤":'≤',
|
|
|
+ "·":'·'
|
|
|
+ } [p1]
|
|
|
+}
|
|
|
+function toJson (html) {
|
|
|
+ const json = parse(html)
|
|
|
+ //console.log('json', json)
|
|
|
+ return json
|
|
|
+}
|
|
|
+function toTextProps (json) {
|
|
|
+ const slices = []
|
|
|
+ let bulletFlag = false
|
|
|
+ //剔除掉ul/li里面多余的\n
|
|
|
+ for(const item of json){
|
|
|
+ if(item.tagName==='ul'||item.tagName==='ol'){
|
|
|
+ item.children = item.children.filter(i=>i.type!=='text')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //console.log('json',json)
|
|
|
+ const _parse = (json, baseStyleObj = {}) => {
|
|
|
+ for (const item of json) {
|
|
|
+ const isBlockTag = 'tagName' in item && ['div', 'li', 'p'].includes(item.tagName)
|
|
|
+ if (isBlockTag && slices.length) {
|
|
|
+ const lastSlice = slices[slices.length - 1]
|
|
|
+ if (!lastSlice.options) lastSlice.options = {}
|
|
|
+ lastSlice.options.breakLine = true
|
|
|
+ }
|
|
|
+ const styleObj = { ...baseStyleObj }
|
|
|
+ const styleAttr = 'attributes' in item ? item.attributes.find(attr => attr.key === 'style') : null
|
|
|
+ if (styleAttr && styleAttr.value) {
|
|
|
+ const styleArr = styleAttr.value.split(';')
|
|
|
+ for (const styleItem of styleArr) {
|
|
|
+ const [_key, _value] = styleItem.split(': ')
|
|
|
+ const [key, value] = [_key ? _key.trim() : '', _value ? _value.trim() : '']
|
|
|
+ if (key && value) styleObj[key] = value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ('tagName' in item) {
|
|
|
+ if (item.tagName === 'em') {
|
|
|
+ styleObj['font-style'] = 'italic'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'strong') {
|
|
|
+ styleObj['font-weight'] = 'bold'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'sup') {
|
|
|
+ styleObj['vertical-align'] = 'super'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'sub') {
|
|
|
+ styleObj['vertical-align'] = 'sub'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'a') {
|
|
|
+ const attr = item.attributes.find(attr => attr.key === 'href')
|
|
|
+ styleObj['href'] = attr || ''
|
|
|
+ }
|
|
|
+ if (item.tagName === 'ul') {
|
|
|
+ styleObj['list-type'] = 'ul'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'ol') {
|
|
|
+ styleObj['list-type'] = 'ol'
|
|
|
+ }
|
|
|
+ if (item.tagName === 'li') {
|
|
|
+ bulletFlag = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ('tagName' in item && item.tagName === 'br') {
|
|
|
+ slices.push({ text: '', options: { breakLine: true } })
|
|
|
+ } else if ('content' in item) {
|
|
|
+ const transStr = /(<|>|“|”|&|‘|’|—|≥|≤|·)/g
|
|
|
+ const text = item.content.replace(/\n/g, '').replace(/ /g, ' ').replace(transStr, replacer)
|
|
|
+ const options = {}
|
|
|
+
|
|
|
+ if (styleObj['font-size']&&styleObj['font-size']!='initial') {
|
|
|
+ options.fontSize = parseInt(styleObj['font-size']) * 0.75
|
|
|
+ }
|
|
|
+ if (styleObj['color']&&styleObj['color']!='initial') {
|
|
|
+ options.color = styleObj['color']
|
|
|
+ }
|
|
|
+ if (styleObj['background-color']&&styleObj['background-color']!='initial') {
|
|
|
+ options.highlight = styleObj['background-color']
|
|
|
+ }
|
|
|
+ if (styleObj['text-decoration-line']) {
|
|
|
+ if (styleObj['text-decoration-line'].indexOf('underline') !== -1) {
|
|
|
+ options.underline = {
|
|
|
+ color: options.color || '#000000',
|
|
|
+ style: 'sng',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (styleObj['text-decoration-line'].indexOf('line-through') !== -1) {
|
|
|
+ options.strike = 'sngStrike'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (styleObj['text-decoration']) {
|
|
|
+ if (styleObj['text-decoration'].indexOf('underline') !== -1) {
|
|
|
+ options.underline = {
|
|
|
+ color: options.color || '#000000',
|
|
|
+ style: 'sng',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (styleObj['text-decoration'].indexOf('line-through') !== -1) {
|
|
|
+ options.strike = 'sngStrike'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (styleObj['vertical-align']) {
|
|
|
+ if (styleObj['vertical-align'] === 'super') options.superscript = true
|
|
|
+ if (styleObj['vertical-align'] === 'sub') options.subscript = true
|
|
|
+ }
|
|
|
+ if (styleObj['text-align']&&styleObj['text-align']!='initial') options.align = styleObj['text-align']
|
|
|
+ if (styleObj['font-weight']) options.bold = styleObj['font-weight'] === 'bold'
|
|
|
+ if (styleObj['font-style']) options.italic = styleObj['font-style'] === 'italic'
|
|
|
+ if (styleObj['font-family']&&styleObj['font-family']!='initial') options.fontFace = styleObj['font-family']
|
|
|
+ if (styleObj['href']) options.hyperlink = {
|
|
|
+ url: styleObj['href']
|
|
|
+ }
|
|
|
+ if (bulletFlag && styleObj['list-type'] === 'ol') {
|
|
|
+ options.bullet = { type: 'number', indent: 20 * 0.75 }
|
|
|
+ options.paraSpaceBefore = 0.1
|
|
|
+ bulletFlag = false
|
|
|
+ }
|
|
|
+ if (bulletFlag && styleObj['list-type'] === 'ul') {
|
|
|
+ options.bullet = { indent: 20 * 0.75 }
|
|
|
+ options.paraSpaceBefore = 0.1
|
|
|
+ bulletFlag = false
|
|
|
+ }
|
|
|
+ slices.push({ text, options })
|
|
|
+ } else if ('children' in item) _parse(item.children, styleObj)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _parse(json)
|
|
|
+ //console.log('slice', slices)
|
|
|
+ return slices
|
|
|
+}
|
|
|
+
|
|
|
+//转换table的数据格式,写入ppt
|
|
|
+function getTableData (data){
|
|
|
+ //data:[{m:'表格数据',mc:{rs:2,cs:1}}] -> [{text:'表格数据',options:{rowspan:2,colspan:1}}]
|
|
|
+ const tableData = []
|
|
|
+ for(let i=0;i<data.length;i++){
|
|
|
+ const row = data[i]
|
|
|
+ const _row =[]
|
|
|
+ for(let j=0;j<row.length;j++){
|
|
|
+ const cell = row[j]
|
|
|
+ let cellOptions = {
|
|
|
+ colspan:cell.mc.cs===0?1:cell.mc.cs,
|
|
|
+ rowspan:cell.mc.rs===0?1:cell.mc.rs
|
|
|
+ }
|
|
|
+ _row.push({
|
|
|
+ text:cell.m,
|
|
|
+ options:cellOptions
|
|
|
+ })
|
|
|
+
|
|
|
+ }
|
|
|
+ if(_row.length) tableData.push(_row)
|
|
|
+ }
|
|
|
+ return tableData
|
|
|
+}
|
|
|
+
|
|
|
+//计算各版式下,对应position在ppt中的位置
|
|
|
+function getPosition(modelId, position) {
|
|
|
+ //找到modelConfig[modelId].elments[position]
|
|
|
+ const model = modelConfig.find((i) => i.modelId === modelId);
|
|
|
+ const { x, y, width, height } = model.elements.find(
|
|
|
+ (i) => i.position === position
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ x:x+'%',
|
|
|
+ y:y+14+'%',
|
|
|
+ width:width+'%',
|
|
|
+ height:height+'%'
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+//计算图像的真实尺寸(object-fit:scale-down)
|
|
|
+function getImgRealSize ({imgWidth,imgHeight,naturalWidth,naturalHeight}){
|
|
|
+ let ratio = naturalWidth/naturalHeight
|
|
|
+ let width = imgHeight*ratio,height=imgHeight
|
|
|
+ if(width>imgWidth){
|
|
|
+ width = imgWidth
|
|
|
+ height = imgWidth/ratio
|
|
|
+ }
|
|
|
+ return {width,height}
|
|
|
+}
|
|
|
+
|
|
|
+function toHex(n){
|
|
|
+ return `${n > 15 ? '' : 0}${n.toString(16)}`;
|
|
|
+}
|
|
|
+function toHexString (colorObj){
|
|
|
+ const { r, g, b,} = colorObj;
|
|
|
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
|
+}
|
|
|
+
|
|
|
+//将rgba转为hex+透明度的格式
|
|
|
+//rgba(91,155,213,0.44) -> {color: "#5b9bd5",transparency: 56}
|
|
|
+function parseRgbaColor (color) {
|
|
|
+ const arr = color.match(/(\d(\.\d+)?)+/g) || [];
|
|
|
+ const res = arr.map((s) => parseInt(s, 10));
|
|
|
+ return {
|
|
|
+ r: res[0],
|
|
|
+ g: res[1],
|
|
|
+ b: res[2],
|
|
|
+ a: parseFloat(arr[3]),
|
|
|
+ }
|
|
|
+}
|
|
|
+//获取shape的格式数据,写入ppt
|
|
|
+function rgbaToHex (rgba) {
|
|
|
+ const colorObj = parseRgbaColor(rgba);
|
|
|
+ return {color:toHexString(colorObj),transparency:(1-colorObj.a)*100}
|
|
|
+}
|
|
|
+function getShapeOptions (el,position,scale){
|
|
|
+ let options = {
|
|
|
+ x:position.x+'%',
|
|
|
+ y:position.y+'%',
|
|
|
+ w:position.w+'%',
|
|
|
+ h:position.h+'%',
|
|
|
+ }
|
|
|
+ if(el.type==='shape'&&el.shapeType==='Rect'){
|
|
|
+ options.fill = rgbaToHex(el.fill)
|
|
|
+ options.points=[
|
|
|
+ {x:0,y:0,moveTo:true},
|
|
|
+ {x:position.w+'%',y:0},
|
|
|
+ {x:position.w+'%',y:position.h+'%'},
|
|
|
+ {x:0,y:position.h+'%'},
|
|
|
+ {close:true}
|
|
|
+ ]
|
|
|
+ options.line = {
|
|
|
+ type:'solid',
|
|
|
+ color:rgbaToHex(el.outline.color).color,
|
|
|
+ transparency:rgbaToHex(el.outline.color).transparency,
|
|
|
+ width:el.outline.width,
|
|
|
+ dashType:el.outline.style === 'solid' ? 'solid' : 'dash',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(el.type==='line'){
|
|
|
+ const width = Math.max(el.start[0], el.end[0])
|
|
|
+ const height = Math.max(el.start[1], el.end[1])
|
|
|
+ //px->英尺 1英尺≈100px(在ppt-width为10英尺的情况下)
|
|
|
+ //906:628.6(编辑页的宽高比) --> 1008:705.6 相当于扩大了1.13倍
|
|
|
+ options.w = (width*scale.x/100)>10?10:width*scale.x/100
|
|
|
+ options.h = (height*scale.y/100)>10?10:height*scale.y/100
|
|
|
+ options.points = [
|
|
|
+ {x:Math.min(el.start[0]*scale.x/100,10),y:Math.min(el.start[1]*scale.y/100,10),moveTo:true},
|
|
|
+ {x:Math.min(el.end[0]*scale.x/100,10),y:Math.min(el.end[1]*scale.y/100,10)}
|
|
|
+ ]
|
|
|
+ options.line={
|
|
|
+ type:'solid',
|
|
|
+ color:rgbaToHex(el.color).color,
|
|
|
+ transparency:rgbaToHex(el.color).transparency,
|
|
|
+ width:el.strokeWidth*0.75,//不缩小下的话线太粗了
|
|
|
+ dashType:el.style==='solid'?'solid':'dash',
|
|
|
+ beginArrowType: el.points[0] ? 'triangle' : 'none',
|
|
|
+ endArrowType: el.points[1] ? 'triangle' : 'none',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return options
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 生成随机码
|
|
|
+function createRandomCode (len = 8) {
|
|
|
+ const charset = `_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
|
|
|
+ const maxLen = charset.length
|
|
|
+ let ret = ''
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ const randomIndex = Math.floor(Math.random() * maxLen)
|
|
|
+ ret += charset[randomIndex]
|
|
|
+ }
|
|
|
+ return ret
|
|
|
+}
|
|
|
+
|
|
|
+// 上传到阿里云
|
|
|
+async function handleUploadToOSS(data){
|
|
|
+ // 生成文件名
|
|
|
+ const t=new Date()
|
|
|
+ const month=moment(t).format('YYYYMM')
|
|
|
+ const day=moment(t).format('YYYYMMDD')
|
|
|
+ const temName=`ppt/${month}/${day}/${createRandomCode(32)}.pptx`
|
|
|
+ console.log('文件名',temName);
|
|
|
+ const url=await useUploadFileToOSS(data,temName)
|
|
|
+ if(!url){
|
|
|
+ showToast('生成ppt失败')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ handlePublishPPT(url)
|
|
|
+}
|
|
|
+
|
|
|
+// 页面转ppt
|
|
|
+async function pageToPPT(){
|
|
|
+ const PPTINS=PPTInit(new pptxgen(),1,'ch')
|
|
|
+ PPTINS.addSlide()
|
|
|
+
|
|
|
+ // ppt正文内容
|
|
|
+ for (let i = 1; i < PPTContentList.length-1; i++) {
|
|
|
+ //console.log(`正在生成,第${i+1}页...`,`lastVisibleItemIndex:`,this.lastVisibleItemIndex)
|
|
|
+
|
|
|
+ let slide = PPTINS.addSlide({ masterName: pptSlideMaster.title });
|
|
|
+ //添加背景图片
|
|
|
+ //slide.background = { path: "/static/pptnextimg.png" };
|
|
|
+ slide.addText(PPTContentList[i].title, {
|
|
|
+ placeholder:"slideTitle",
|
|
|
+ x:'10%',
|
|
|
+ y:'5.5%',
|
|
|
+ w:'68%',
|
|
|
+ h:'7%',
|
|
|
+ color:'333333'
|
|
|
+ });
|
|
|
+ const elements = PPTContentList[i].elements;
|
|
|
+ const elLength = elements.length;
|
|
|
+ for (let j = 0; j < elLength; j++) {
|
|
|
+ let imgData = null,textData = null,imgData2 = null,imgData2Obj=null,sheetData=null;
|
|
|
+ if (elements[j].type === "chart") {
|
|
|
+ console.log("img/chart...");
|
|
|
+ const chartId=`chart${elements[j].chartId}_${i}_${elements[j].position}`
|
|
|
+ // 判断图有没有被删除
|
|
|
+ const chartIsDel=$(`#${chartId} .empty-img`)
|
|
|
+ if(chartIsDel.length==0){
|
|
|
+ //将svgDom转为base64 png,返回一个base64字符串
|
|
|
+ imgData = await changeUrl(chartId)
|
|
|
+ }
|
|
|
+ } else if (elements[j].type === 'text'){
|
|
|
+ console.log('text...')
|
|
|
+ textData = toTextProps(toJson(elements[j].richContent))
|
|
|
+ }else if (elements[j].type==='image'){
|
|
|
+ console.log("img/image...")
|
|
|
+ imgData2 = PPTContentList[i].elements[j].src
|
|
|
+ imgData2Obj = {
|
|
|
+ imgWidth:$(`#image_${i}_${elements[j].position}`).width(),
|
|
|
+ imgHeight:$(`#image_${i}_${elements[j].position}`).height(),
|
|
|
+ naturalWidth:document.getElementById(`image_${i}_${elements[j].position}`).naturalWidth,
|
|
|
+ naturalHeight:document.getElementById(`image_${i}_${elements[j].position}`).naturalHeight
|
|
|
+ }
|
|
|
+ }else if (elements[j].type==='sheet'){
|
|
|
+ console.log('table...')
|
|
|
+ const sheetId = elements[j].sheetId
|
|
|
+ const res=await apiSheetInfoByCode({UniqueCode:sheetId})
|
|
|
+ if(res.Ret===200){
|
|
|
+ sheetData = getTableData(res.Data?.TableInfo?.TableDataList||[])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const { x, y, width, height } = getPosition(
|
|
|
+ PPTContentList[i].modelId,
|
|
|
+ elements[j].position
|
|
|
+ );
|
|
|
+ if (imgData) {
|
|
|
+ slide.addImage({
|
|
|
+ data:imgData,
|
|
|
+ x: x,
|
|
|
+ y: y,
|
|
|
+ w: width,
|
|
|
+ h: height,
|
|
|
+ size: { type: "contain" },
|
|
|
+ });
|
|
|
+ }else if (textData){
|
|
|
+ slide.addText(textData,{
|
|
|
+ x:x,
|
|
|
+ y:y,
|
|
|
+ w:width,
|
|
|
+ h:height,
|
|
|
+ margin:10,
|
|
|
+ fontSize: 16*0.75,
|
|
|
+ valign:'top'
|
|
|
+ })
|
|
|
+ }else if(imgData2){
|
|
|
+ //console.log('src',imgData2)
|
|
|
+ const realSize = getImgRealSize(imgData2Obj)
|
|
|
+ const percentWidth = Number(width.substring(0,width.length-1))
|
|
|
+ const percentHeight = Number(height.substring(0,height.length-1))
|
|
|
+ const offsetX = realSize.width===imgData2Obj.imgWidth?0:(percentWidth-(realSize.width/imgData2Obj.imgWidth*percentWidth))/2
|
|
|
+ const offsetY = realSize.height===imgData2Obj.imgHeight?0:(percentHeight-(realSize.height/imgData2Obj.imgHeight*percentHeight))/2
|
|
|
+ const realX = Number(x.substring(0,x.length-1))+offsetX
|
|
|
+ const realY = Number(y.substring(0,y.length-1))+offsetY
|
|
|
+ /* const realWidth = offsetX===0?width:(realSize.width/imgData2Obj.imgWidth*percentWidth)
|
|
|
+ const realHeight = offsetY===0?height:(realSize.height/imgData2Obj.imgHeight*percentHeight) */
|
|
|
+ //console.log('position x',x,' y',y,' width',width,' height',height)
|
|
|
+ //console.log('x y',realX,realY,'w h',realWidth,realHeight)
|
|
|
+ slide.addImage({
|
|
|
+ path:imgData2+'?v='+new Date().getTime(),
|
|
|
+ x:realX+'%',
|
|
|
+ y:realY+'%',
|
|
|
+ w:offsetX===0?width:(realSize.width/imgData2Obj.imgWidth*percentWidth)+'%',
|
|
|
+ h:offsetY===0?height:(realSize.height/imgData2Obj.imgHeight*percentHeight)+'%',
|
|
|
+ size:{type:"contain"}
|
|
|
+ })
|
|
|
+ }else if(sheetData){
|
|
|
+ slide.addTable(sheetData,{
|
|
|
+ x:x,
|
|
|
+ y:y,
|
|
|
+ w:width,
|
|
|
+ h:height,
|
|
|
+ border:{type:'solid',pt:1}
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //在添加完版式后,添加图层元素
|
|
|
+ const layers = PPTContentList[i].layers||[]
|
|
|
+ const layersLength = layers.length
|
|
|
+ for(let j = 0; j < layersLength; j++){
|
|
|
+ const {percentageTop,percentageLeft,percentageWidth,percentageHeight} = layers[j]
|
|
|
+ //percent单位基于预留标题位置,所以top和height的百分比需要调整
|
|
|
+ let position = {x:percentageLeft*100,y:percentageTop*100*0.86+14,w:percentageWidth*100,h:percentageHeight*100*0.86}
|
|
|
+ //type为shape&line
|
|
|
+ if(['shape','line'].includes(layers[j].type)){
|
|
|
+ // const scale = calcScale({w:906,h:906*0.7},{w:$('.ppt-item').width(),h:$('.ppt-item').width()*this.coefficient})
|
|
|
+ let options = getShapeOptions(layers[j],position,1)
|
|
|
+ //console.log('options',options)
|
|
|
+ slide.addShape('custGeom',options)
|
|
|
+ }
|
|
|
+ //type为text,则转换内部文字
|
|
|
+ if(layers[j].type==='text'){
|
|
|
+ let textData = toTextProps(toJson(layers[j].richContent))
|
|
|
+ slide.addText(textData,{
|
|
|
+ x:position.x+'%',
|
|
|
+ y:position.y+'%',
|
|
|
+ w:position.w+'%',
|
|
|
+ h:position.h+'%',
|
|
|
+ margin:10,
|
|
|
+ fontSize: 16*0.75,
|
|
|
+ valign:'top'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //添加封底
|
|
|
+ let back = PPTINS.addSlide()
|
|
|
+ let backImg = $(`#ppt-last-page img`)[0].src
|
|
|
+ back.addImage({
|
|
|
+ path: backImg,
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ w:'100%',
|
|
|
+ h: '100%',
|
|
|
+ size: { type: "contain" },
|
|
|
+ })
|
|
|
+
|
|
|
+ //为了把封面放到第一页,操作pptx.slides达不成想要的效果,于是弄了个pptx2
|
|
|
+ //将封面放在最后生成是因为htmlToCanvans占用太多内存会导致页面假死
|
|
|
+ let pptx2 = PPTInit(new pptxgen(),1);
|
|
|
+ //添加封面
|
|
|
+ let cover = pptx2.addSlide()
|
|
|
+ let coverImg = $(`#ppt-cover-page .pptbg`)[0].src
|
|
|
+ cover.addImage({
|
|
|
+ path: coverImg,
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ w:'100%',
|
|
|
+ h: '100%',
|
|
|
+ size: { type: "contain" },
|
|
|
+ })
|
|
|
+ //生成的ppt需要可以在封面页更改标题和类型,所以封面信息手动写入
|
|
|
+ const coverInfo = [
|
|
|
+ {text:'—————————————————————————————————\n',options:{fontSize:16*0.75,breakLine:true}},
|
|
|
+ {text:PPTContentList[0].Title,options:{fontSize:28*0.75,breakLine:true}},
|
|
|
+ {text:`\n— 弘则弥道(上海)投资咨询有限公司 ● ${PPTContentList[0].ReportType} —`,
|
|
|
+ options:{fontSize:16*0.75,breakLine:false}},
|
|
|
+ {text:'\nFICC研究部',options:{fontSize:16*0.75,breakLine:true}},
|
|
|
+ {text:PPTContentList[0].PptDate,options:{fontSize:16*0.75,breakLine:true}},
|
|
|
+ {text:'\n—————————————————————————',options:{fontSize:16*0.75,breakLine:true}}
|
|
|
+ ]
|
|
|
+ cover.addText(coverInfo,{
|
|
|
+ x:'38%',
|
|
|
+ y:'50%',
|
|
|
+ w:'60%',
|
|
|
+ h:'28%',
|
|
|
+ color:'ffffff',
|
|
|
+ align:'center',
|
|
|
+ fontFace:'SimHei'
|
|
|
+ })
|
|
|
+ //遍历pptx.slides,重新给每一项的部分属性赋值,再推入pptx2.slides中
|
|
|
+ //第一页不需要,因为是空白的
|
|
|
+ for(let i=1;i<PPTINS.slides.length;i++){
|
|
|
+ let item = _.cloneDeep(PPTINS.slides[i])
|
|
|
+ item._name = `Slide ${i+1}`
|
|
|
+ item._slideNum = i+1
|
|
|
+ item._rId = cover._rId+i
|
|
|
+ item._slideId = cover._slideId+i
|
|
|
+ pptx2._slides.push(item)
|
|
|
+ }
|
|
|
+ pptx2.write('blob').then((data)=>{
|
|
|
+ LoadingINS.close()
|
|
|
+ // 上传到阿里云oss
|
|
|
+ handleUploadToOSS(data)
|
|
|
+ })
|
|
|
+ // pptx2.writeFile({ fileName: "test.pptx" }) //本地测试可直接用该方法生成ppt文件
|
|
|
+}
|
|
|
+
|
|
|
+// 发布ppt
|
|
|
+function handlePublishPPT(url){
|
|
|
+ apiPPTPublish({
|
|
|
+ PptId:Number(pptId),
|
|
|
+ PptxUrl:url
|
|
|
+ }).then(res=>{
|
|
|
+ if(res.Ret===200){
|
|
|
+ showToast('发布成功')
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 发布ppt入口方法
|
|
|
+ * @param data ppt内容list
|
|
|
+ * @param id pptid
|
|
|
+ */
|
|
|
+export async function usePPTPublish(data,id){
|
|
|
+ PPTContentList=data
|
|
|
+ pptId=id
|
|
|
+ if(!checkPPT()) return
|
|
|
+ console.log('校验通过');
|
|
|
+ LoadingINS=showLoadingToast({
|
|
|
+ message: "发布中...",
|
|
|
+ duration: 0,
|
|
|
+ forbidClick: true,
|
|
|
+ })
|
|
|
+ pageToPPT()
|
|
|
+
|
|
|
+
|
|
|
+}
|