obj.offsetHeight与obj.style.height的区别

本文探讨了HTML元素位置属性offsetTop与style.top之间的不同之处,包括返回类型、可读写性及默认值等方面,并延伸讨论了类似属性的对比。

我们知道 offsetTop 可以获得 HTML 元素距离上方或外层元素的位置,style.top 也是可以的,二者的区别是:

一、offsetTop 返回的是数字,而 style.top 返回的是字符串,除了数字外还带有单位:px。

二、offsetTop 只读,而 style.top 可读写。

三、如果没有给 HTML 元素指定过 top 样式,则 style.top 返回的是空字符串。

offsetLeft 与 style.left、offsetWidth 与 style.width、offsetHeight 与 style.height 也是同样道理。

<!-- * @Description: * @autor: liuleilei * @Date: 2025-06-09 13:52:09 * @LastEditors: liuleilei * @LastEditTime: 2025-06-09 15:10:50 --> <template> <div class="drag-sign-seal"> <div id="elesign" class="elesign scrollbar vc-page"> <el-row style="height: 100%"> <div class="page"> <el-button size="small" class="btn-outline-default" @click="prevPage" >上一页</el-button > <el-button size="small" class="btn-outline-default" @click="nextPage" >下一页</el-button > <el-button size="small" class="btn-outline-default" >{{ pageNum }}/{{ numPages }}页</el-button > <el-input-number size="small" style="margin-left: 0.1rem; border-radius: 5px" v-model="currentPage" :min="1" :max="numPages" label="输入页码" @blur="nullity" ></el-input-number> <el-button size="small" class="btn-outline-default" @click="cutover" >跳转</el-button > <el-button size="small" class="btn-outline-default" @click="zoomOut">缩小</el-button> <el-button size="small" class="btn-outline-default">{{ Math.round(currentScale * 100) }}%</el-button> <el-button size="small" class="btn-outline-default" @click="zoomIn">放大</el-button> </div> <div class="col-left-box" id="col-left-box" ref="colLeftBox"> <el-col :span="20" class="pCenter col-left" id="col-left" ref="colLeft"> <div class="canvas-div-box" ref="canvasDivBox"> <div class="canvas-div" id="canvas-div" ref="canvasDiv" v-loading="showLoadingChild"> <!-- pdf部分 --> <canvas style="width: 100%; height: 100%;" id="the-canvas"/> <!-- 盖章部分 --> <canvas style="width: 100%; height: 100%; border: none !important" id="ele-canvas"></canvas> </div> </div> </el-col> </div> <el-col :span="4" class="col-right"> <el-button size="small" type="primary" @click="removeSignature"> 清除签章</el-button > <el-tree :data="menuTree" :props="elTreeProps" default-expand-all :expand-on-click-node="false" > <div slot-scope="{ node, data }"> <div class="custom-tree-node" v-if="!data.pid && !data.isOwnSign"> {{ data.name }} </div> <draggable v-if="data.pid" class="draggable-list scrollbar" v-model="mainImagelist" :group="{ name: 'itext', pull: 'clone' }" :sort="false" @end="end($event, data)" > <transition-group type="transition"> <template> <span :key="data.url"> <img :src="data.url" width="100%;" height="100%" class="imgstyle" /> </span> <div v-if="data.name">{{ data.name }}</div> </template> </transition-group> </draggable> <div v-if="data.sealName" style="text-align: center;white-space: pre-wrap;"> {{ data.sealName }} </div> </div> </el-tree> </el-col> </el-row> </div> <!-- 加载中遮罩 --> <van-overlay :show="showLoadingChild"> <van-loading class="van-loading" style="height: 100%"> </van-loading> </van-overlay> </div> </template> <script> import { getPdfFile, findSignListByUserId, findCanUseSealList, getFileServerConfig, } from "@/api/module/formManggeApi"; import { fabric, cache } from "fabric"; import draggable from "vuedraggable"; import pdf from "vue-pdf"; import CMapReaderFactory from "vue-pdf/src/CMapReaderFactory.js"; const SIGN = "SIGN"; // 签字 const STAMP = "STAMP"; // 印章 export default { name: "drag-sign-seal", props: { entity: { type: Object, default: () => { return {}; }, }, isPortrait: { type: Boolean, default: true }, sealParameter: { type: Object, default: () => { return {}; }, }, fileObj: { type: Object, default: () => { return {}; }, }, sealSignPositionFieldMap: { type: Object, default: () => { return {}; }, }, isOwnSign: { type: Boolean, default: false, }, addr: { type: String, default: '', }, pageN: { type: Number, default: 1, }, isScroll: { type: Boolean, default: false, } }, components: { draggable, }, data() { return { touchStartHandler: null, touchMoveHandler: null, fileUrl: '', menuTree: [ { name: "电子印章", children: [], pid: null, isOwnSign: false }, { name: "本人签名", pid: null, children: [], isOwnSign: false }, ], elTreeProps: { children: "children", label: "name", }, showLoadingChild: false, signs: {}, //pdf预览 pdfUrl: "", pdfDoc: null, numPages: 1, pageNum: 1, scale: 2, currentScale: 1, minScale: 0.5, // 最小缩放比例 maxScale: 5.0, // 最大缩放比例 scaleStep: 0.1, // 缩放步长 pageRendering: false, pageNumPending: null, canvas: null, ctx: null, canvasEle: null, whDatas: null, mainImagelist: [], signList: [], sealList: [], sealMap: {}, activeName: "", currItemIndex: {}, dateList: [], fileType: "", previewFile: null, mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", currentPage: 1, mobileScale: 0.2, objectMovingFlag:false, }; }, computed: { userInfo() { return this.$store.state.user; }, }, watch: { fileObj: { handler() { if (!this.fileObj || !this.fileObj?.fileId) { return; } this.init(); }, deep: true, immediate: true, }, whDatas: { handler() { // this.showLoading = true; if (this.whDatas) { // this.showLoading = false; this.renderFabric(); this.canvasEvents(); } }, immediate: false, deep: true, }, pageNum: { handler(newVal, oldVal) { this.commonSign(this.pageNum); this.queueRenderPage(this.pageNum); this.currentPage = newVal; }, deep: true, }, sealSignPositionFieldMap: { handler(n) { if (n && !n.length) { this.canvasEle.remove(this.canvasEle.clear()) } }, deep: true } }, created() {}, mounted() { if (!this.isScroll) { this.initScroll() } this.enableScroll(); }, methods: { enableScroll() { let _this = this; const container = this.$refs.canvasDivBox; let isDown = false; let startX, startY; let scrollLeft, scrollTop; // 鼠标事件 container.addEventListener('mousedown', (e) => { if(_this.objectMovingFlag){ return; } isDown = true; startX = e.pageX - container.offsetLeft; startY = e.pageY - container.offsetTop; scrollLeft = container.scrollLeft; scrollTop = container.scrollTop; }); container.addEventListener('mouseleave', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('mouseup', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('mousemove', (e) => { if(_this.objectMovingFlag){ return; } if (!isDown) return; e.preventDefault(); const x = e.pageX - container.offsetLeft; const y = e.pageY - container.offsetTop; const walkX = (x - startX) * 2; // 水平滚动速度 const walkY = (y - startY) * 2; // 垂直滚动速度 container.scrollLeft = scrollLeft - walkX; container.scrollTop = scrollTop - walkY; }); // 触摸事件支持 container.addEventListener('touchstart', (e) => { if(_this.objectMovingFlag){ return; } isDown = true; startX = e.touches[0].pageX - container.offsetLeft; startY = e.touches[0].pageY - container.offsetTop; scrollLeft = container.scrollLeft; scrollTop = container.scrollTop; }); container.addEventListener('touchend', () => { if(_this.objectMovingFlag){ return; } isDown = false; }); container.addEventListener('touchmove', (e) => { if(_this.objectMovingFlag){ return; } if (!isDown) return; const x = e.touches[0].pageX - container.offsetLeft; const y = e.touches[0].pageY - container.offsetTop; const walkX = (x - startX) * 2; const walkY = (y - startY) * 2; container.scrollLeft = scrollLeft - walkX; container.scrollTop = scrollTop - walkY; }); }, initScroll() { const scrollableDiv = document.getElementById('col-left'); const scrollableDiv1 = document.getElementById('app'); let startY1 = 0; let scrollTop1 = 0; // 定义 touchstart 和 touchmove 处理函数 let startY = 0; let scrollTop = 0; this.touchStartHandler = (e) => { startY = e.touches[0].clientY; scrollTop = scrollableDiv.scrollTop; startY1 = e.touches[0].clientY; scrollTop1 = scrollableDiv1.scrollTop; }; this.touchMoveHandler = (e) => { e.preventDefault(); // 阻止默认滚动行为 const currentY = e.touches[0].clientY; const diff = currentY - startY; scrollableDiv.scrollTop = scrollTop - diff; const currentY1 = e.touches[0].clientY; const diff1 = currentY1 - startY1; scrollableDiv1.scrollTop = scrollTop1 - diff1; }; // 添加事件监听 document.addEventListener('touchstart', this.touchStartHandler); document.addEventListener('touchmove', this.touchMoveHandler, { passive: false }); }, removeScrollListeners() { // 移除事件监听 if (this.touchStartHandler) { document.removeEventListener('touchstart', this.touchStartHandler); } if (this.touchMoveHandler) { document.removeEventListener('touchmove', this.touchMoveHandler); } }, async init() { await this.getFileServerConfig(); this.initProperties(); // this.findSignByUserId(); // this.findCanUseSealList(); this.setPdfArea(); // 监听键盘事件 document.addEventListener("keydown", (event) => { // 检查按下的是否是删除键(Delete键或Backspace键) if (event.key === "Delete" || event.key === "Backspace") { const activeObject = this.canvasEle.getActiveObject(); if (activeObject) { // 删除活动对象 this.canvasEle.remove(activeObject); // 清空活动对象 this.canvasEle.setActiveObject(null); // 渲染画布以显示更改 this.canvasEle.renderAll(); } } }); // 我的表单新增时,只有签名 if (this.isOwnSign) { this.menuTree[0].isOwnSign = true; } // 执行页码 this.pageNumber(); }, pageNumber() { this.currentPage = this.pageN; this.cutover(); }, /** * @description: 设置PDF预览区域内容 * @return {*} */ async setPdfArea() { let pdfUrl = ""; if (!this.fileObj.fileId || this.fileObj.fileId === '') { this.$toast("没有需处理的文件"); return; } if (this.isPdf()) { const pdfFile = this.$STDCOMMON.fileOperation.base64ToFile( this.fileObj.content, this.fileName, this.mimeType ); pdfUrl = URL.createObjectURL(pdfFile); } else { this.showLoadingChild = true; await this.getFile(); this.showLoadingChild = false; if (!this.previewFile) { this.pageTotal = 0; return; } pdfUrl = URL.createObjectURL(this.previewFile); } this.$nextTick(() => { this.showpdf(pdfUrl); }); }, /** * @description: 渲染pdf,到时还会盖章信息,在渲染时,同时显示出来,不应该在切换页码时才显示印章信息 * @return {*} */ showpdf(pdfUrl) { this.canvas = document.getElementById("the-canvas"); this.ctx = this.canvas.getContext("2d"); this.pdfUrl = pdf.createLoadingTask({ url: pdfUrl, // 解决pdf文件,后台填充的文本不显示问题,字体有关 CMapReaderFactory, }); this.pdfUrl.promise .then((pdfDoc_) => { this.pdfDoc = pdfDoc_; this.numPages = this.pdfDoc.numPages; this.renderPage(this.pageNum).then(() => { const pdfContainer = document.getElementById('col-left'); this.renderPdf({ width: pdfContainer.clientWidth, height: pdfContainer.clientHeight, }); this.commonSign(this.pageNum, true); }); }) .catch((err) => { this.pageTotal = 0; // this.showLoading = false; }); }, // 放大 zoomIn() { this.currentScale = Math.min(this.currentScale + this.scaleStep, this.maxScale); this.applyZoom(); }, // 缩小 zoomOut() { this.currentScale = Math.max(this.currentScale - this.scaleStep, this.minScale); this.applyZoom(); }, // 应用缩放 async applyZoom() { document.getElementById('canvas-div').style.transform = `scale(${this.currentScale})`; }, /** * @description: 初始化当前页的PDF内容,canvas宽度高度 * @param {*} num 当前页 * @return {*} */ renderPage(num) { let _this = this; this.pageRendering = true; return this.pdfDoc.getPage(num).then((page) => { // 获取目标 div 的宽度 const pdfContainer = document.getElementById('col-left'); const containerWidth = pdfContainer.clientWidth; // 获取页面视图的原始尺寸 const originalViewport = page.getViewport({ scale: 1 }); // 计算适应 div 宽度的缩放比例 const margin = 10; // 边距设置 this.mobileScale = (containerWidth) / originalViewport.width; // 重新获取视口,根据计算的 scale let viewport = page.getViewport({ scale: this.scale }); //设置视口大小 scale数值越大,清晰度越高,占用内存越高 _this.canvas.height = viewport.height; _this.canvas.width = viewport.width; _this.canvasEle && _this.canvasEle.setHeight(viewport.height); _this.canvasEle && _this.canvasEle.setWidth(viewport.width); let renderContext = { canvasContext: _this.ctx, viewport: viewport, }; let renderTask = page.render(renderContext); renderTask.promise.then(() => { _this.pageRendering = false; if (_this.pageNumPending !== null) { this.renderPage(_this.pageNumPending); _this.pageNumPending = null; } }); }); }, /** * @description: 设置绘图区域宽高 * @return {*} */ renderPdf(data) { this.whDatas = data; }, queueRenderPage(num) { if (this.pageRendering) { this.pageNumPending = num; } else { this.renderPage(num); } }, /** * @description: 翻页展示盖章信息 * @param {*} pageNum 第几页 * @param {*} isFirst 是否第一页 * @return {*} */ commonSign(pageNum, isFirst = false) { if (isFirst == false) this.canvasEle.remove(this.canvasEle.clear()); //清空页面所有签章 if (!this.signs || !Object.keys(this.signs).length) return false; let datas = this.signs[pageNum]; if (datas != null && datas != undefined) { for (let index in datas) { const obj = datas[index]; const params = { sealUrl: obj.type === "i-text" ? obj.text : obj.sealUrl, left: obj.left , top: obj.top , index: obj.index, scaleX: obj.scaleX, scaleY: obj.scaleY, type: obj.type, useId: obj.useId, fileId: obj.fileId, hasControls: obj.type === SIGN, angle:obj.angle || 0, selectable: obj?.selectable, creatorId: obj?.creatorId, creatorName: obj?.creatorName, createTime: obj?.createTime, writeSignBase64: obj?.writeSignBase64 }; this.addSeal(params); } } }, /** * @description: 生成绘图区域,并设置绘图区域的宽高,位置预览的canvas重叠 * @return {*} */ renderFabric() { let canvaEle = document.querySelector("#ele-canvas"); const thecanvas = document.querySelector("#the-canvas"); canvaEle.width = document.querySelector("#the-canvas").clientWidth; canvaEle.height = document.querySelector("#the-canvas").clientHeight; this.canvasEle = new fabric.Canvas(canvaEle); let container = document.querySelector(".canvas-container"); container.style.position = "absolute"; container.style.top = "3px"; // container.style.left = (width - document.querySelector("#the-canvas").clientWidth) / 2 - 3 + "px"; this.pageNumber(); }, /** * @description: 相关事件操作,比如在绘图区域点击事件,元素的拖拽事件 * @return {*} */ canvasEvents() { let _this = this; // 鼠标在绘图区域的点击事件,鼠标按下时,如果在绘制的对象上,则使对象进入编辑状态 this.canvasEle.on("mouse:down", (options) => { var activeObject = options.target; if (activeObject && activeObject?.selectable) { _this.objectMovingFlag = true; } if (activeObject && activeObject.isEditing) { return; } if (activeObject && activeObject.type === "textbox") { activeObject.enterEditing(); activeObject.selectAll(); } this.canvasEle.renderAll(); }); // 拖拽边界 不能将图片拖拽到绘图区域外 this.canvasEle.on("object:moving", function (e) { _this.objectMovingFlag = true; var obj = e.target; // if object is too big ignore if ( obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width ) { return; } obj.setCoords(); // top-left corner if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) { obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top); obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left); } // bot-right corner if ( obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width ) { obj.top = Math.min( obj.top, obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top ); obj.left = Math.min( obj.left, obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left ); } }); // 监听所有对象的拖动结束 this.canvasEle.on('object:modified', function(e) { _this.objectMovingFlag = false ; }); }, /** * @description: 拖拽时添加fabric对象,签名,印章,文本等 * @param {*} sealUrl 如果是图片,则是图片的url,如果是文本,则是文本内容 * @param {*} left 左间距 * @param {*} top 顶部间距 * @param {*} index 索引:mainImagelist的 * @param {*} scaleX x轴比例尺 * @param {*} scaleY y轴比例尺 * @param {*} type 类型:TEXT,SIGN,STAMP * @param {*} useId 签名id,或者印章id * @param {*} fileId 签名或印章的文件id * @param {*} hasControls 是否支持编辑 * @param {*} isInit * @return {*} sealUrl, left, top, index, scaleX, scaleY, type, useId, fileId, hasControls = true, isInit = false */ addSeal(params) { // 缩放比例 const scaling = this.mobileScale / 2; const scrollableDiv = document.getElementById('canvas-div'); const scrollTop = scrollableDiv.scrollTop; // 签名和印章使用图片元素 fabric.Image.fromURL(params.sealUrl, (oImg) => { oImg.hasControls = params.hasControls; oImg.set({ left: params.left * scaling, top: (params.top + scrollTop) * scaling, scaleX: params.scaleX || 1, scaleY: params.scaleY || 1, index: params.index, hasControls: params.hasControls, type: params.type, useId: params.useId, fileId: params.fileId, sealUrl:params.fileId ? `${this.fileUrl}/dl/view/${params.fileId}`:params?.writeSignBase64, pageNum: this.pageNum, businessId: this.entity.id, sealSignId: params.sealSignId, pageWidth: this.canvas.width, pageHeight: this.canvas.height, newFieldFlag: params.newFieldFlag, selectable: params.selectable, creatorId: params?.creatorId, creatorName: params?.creatorName, createTime: params?.createTime, writeSignBase64: params?.writeSignBase64 }); oImg.lockRotation = true; // 禁止旋转 // 签名禁止左右上下拖动,只支持按比例拖动 oImg.setControlVisible("mt", false); oImg.setControlVisible("mb", false); oImg.setControlVisible("ml", false); oImg.setControlVisible("mr", false); oImg.set({ angle: params.angle }); // oImg.scale(0.5); //图片缩小一 if(params.type === STAMP){ oImg.scale(0.38); } else { oImg.scale(scaling * params.scaleX); } this.$nextTick(()=>{ this.canvasEle && this.canvasEle.add(oImg); }) }); }, getSealSignPositionFieldList() { const data = this.confirmSignature(); const result = []; for (const key in data) { const cache = data[key]; if (JSON.stringify(cache) === "{}") { continue; } for (const k in cache) { const ele = cache[k]; if (JSON.stringify(ele) === "{}") { continue; } result.push(ele); } } if (result && result.length) { const scaling = this.mobileScale / 2; result.forEach(i => { // if (i.type === SIGN) { this.$set(i, 'scaleX', i.scaleX / scaling); this.$set(i, 'scaleY', i.scaleY / scaling); // this.$set(i, 'pageHeight', i.pageHeight / scaling); this.$set(i, 'bly', i.bly / scaling); // } }) } return result; }, /** * @description: 判断是否是pdf文件 * @return {*} */ isPdf() { if (this.fileObj?.fileName) { const arr = this.fileObj?.fileName.split("."); this.fileType = arr[arr.length - 1]; } return this.fileType && this.fileType.toLowerCase() === "pdf"; }, async getFile() { return await getPdfFile({ id: this.fileObj?.id, fileId: this.fileObj?.fileId, fileName: this.fileObj?.fileName, type: this.fileType, content: this.fileObj?.content, }) .then((res) => { if (!res.success) { this.previewFile = null; return; } if (!res.data) { this.previewFile = null; return; } this.previewFile = this.$STDCOMMON.fileOperation.base64ToFile( res.data, this.fileName, this.mimeType ); }) .catch(() => { this.previewFile = null; }); }, /** * @description: 上一页点击事件:确认签章位置等属性并保存到缓存 * @return {*} */ prevPage() { this.confirmSignature(); if (this.pageNum <= 1) { return; } this.pageNum--; this.currentPage = this.pageNum; this.$emit('page', this.currentPage); }, /** * @description: 下一页点击事件:确认签章位置等属性并保存到缓存 * @return {*} */ nextPage() { this.confirmSignature(); if (this.pageNum >= this.numPages) { return; } this.pageNum++; this.currentPage = this.pageNum; this.$emit('page', this.currentPage); }, /** * @description: 跳转按钮事件:确认签章位置等属性并保存到缓存 * @return {*} */ cutover() { this.confirmSignature(); this.pageNum = this.currentPage; if (!this.currentPage) { this.pageNum = 1; this.currentPage = 1; } this.$emit('page', this.currentPage); }, /** * @description: 确认签章位置并保存到缓存 * @return {*} */ confirmSignature() { let data = this.canvasEle.getObjects(); //获取当前页面内的所有签章信息 let caches = JSON.parse(JSON.stringify(this.signs)); //获取缓存字符串后转换为对象 let signDatas = {}; //存储当前页的所有签章信息 let i = 0; for (var val of data) { signDatas[i] = { width: val.width, // 签字,盖章,文本元素的宽度和高度 height: val.height, top: val.top / (this.mobileScale / 2), // 据顶部距离 left: val.left / (this.mobileScale / 2), // 距左侧距离 angle: val.angle, translateX: val.translateX, translateY: val.translateY, scaleX: val.scaleX, // 比例尺x scaleY: val.scaleY, // 比例尺y pageNum: val.pageNum, // pdf第几页 sealUrl: val.sealUrl, // 签字或印章文件的地址 useId: val.useId, // 签字或印章数据的id fileId: val.fileId, // 签字或印章的文件id index: val.index, // 索引,目前用不到 type: val.type, // 元素类型:i-text, image type: val.type, // 类型,签字,盖章,文本 businessId: val.businessId, // 表单id sealSignId: val.sealSignId, // 印章/签名对应的业务id hasControls: val.hasControls, // 是否可编辑,拖动操作 text: val.text, // 如果是文本:文本内容 pageWidth: val.pageWidth, // 页面宽度 pageHeight: val.pageHeight, // 页面高度 tlx: val.aCoords.tl.x, //左上角x tly: val.aCoords.tl.y, // 左上角y trx: val.aCoords.tr.x, // 右上角x try: val.aCoords.tr.y, // 右上角y blx: val.aCoords.bl.x + 3, // 左下角x bly: val.aCoords.bl.y, // 左下角y brx: val.aCoords.br.x, // 右下角x bry: val.aCoords.br.y, // 右下角 y newFieldFlag: val?.newFieldFlag, scale:this.scale, selectable: val.selectable, // 是否可拖动 creatorId: val?.creatorId, creatorName: val?.creatorName, createTime: val?.createTime, writeSignBase64: val?.writeSignBase64 }; i++; } if (caches == null) { caches = {}; caches[this.pageNum] = signDatas; } else { caches[this.pageNum] = signDatas; } this.signs = caches; return this.signs; }, initProperties() { this.pageNum = 1; this.numPages = 1; this.pdfUrl = ""; this.whDatas = null; // 处理默认数据,只能推拽自己的盖章和签字 let sealSignPositionFieldMap = this.sealSignPositionFieldMap; if (sealSignPositionFieldMap) { Object.keys(sealSignPositionFieldMap).forEach((key) => { let list = sealSignPositionFieldMap[key]; list.forEach((item) => { item.selectable = item.creatorId === this.userInfo.id; if(!item.fileId && item?.writeSignBase64){ item.sealUrl = item?.writeSignBase64; } }); }); } this.signs = sealSignPositionFieldMap; }, removeSignature() { this.canvasEle.remove(this.canvasEle.getActiveObject()); }, /** * @description: 拖动结束,判断是否在指定的区域,如果在预览区域内,则将印章放进去,否则不绘制 * @param {*} e * @return {*} */ end(e, obj) { const canvasDiv = this.$refs.canvasDiv; // 指定区域的DOM元素 const rect = canvasDiv.getBoundingClientRect(); const scrollableDiv = document.getElementById('canvas-div'); const scrollTop = scrollableDiv.scrollTop; const scrollLeft = scrollableDiv.scrollLeft; if ( e.originalEvent.changedTouches[0].pageX >= rect.left && e.originalEvent.changedTouches[0].pageX <= rect.right && e.originalEvent.changedTouches[0].pageY >= rect.top && e.originalEvent.changedTouches[0].pageY <= rect.bottom ) { // end元素中取originalEvent 里边有4组xy,多试一下该用哪个 const type = obj.type; let params = { sealUrl: obj.url, left: e.originalEvent.changedTouches[0].clientX + scrollLeft, top: e.originalEvent.changedTouches[0].clientY, index: e.newDraggableIndex, scaleX: 1, scaleY: 1, type: type, useId: obj.useId, fileId: obj.fileId, sealSignId: obj.sealSignId, hasControls: type === SIGN, isInit: true, newFieldFlag: "1", angle:obj.angle || 0, selectable: true, // false禁止选择,从而禁止拖动 }; this.addSeal(params); } else { this.$toast("请将个人签名或电子印章拖入指定区域内"); } }, /** * @description: 查询当前登录人的签名信息,并处理成需要的格式 * @return {*} */ findSignByUserId() { return findSignListByUserId(this.userInfo.id) .then((res) => { if (!res.success || !res.data || !res.data.length) { return; } res.data = res.data.filter(item => { return item.fileId != null && item.fileId !== ''; }); if (!res.success || !res.data || !res.data.length) { return; } let list = []; res.data.forEach((item) => { list.push({ url: item.content, useId: this.userInfo.id, fileId: item.fileId, type: SIGN, pid: SIGN, sealSignId: item.businessId, hasControls: true, }); }); this.menuTree[1].children = list; this.mainImagelist.push(...list); }) .catch((err) => { this.$message.error(err); }); }, findCanUseSealList() { findCanUseSealList({ userId: this.userInfo.id, moduleCode: 'smartsFormCenterAudit', }) .then((res) => { if (!res.success || !res.data) { return; } let list = []; res.data.forEach((item) => { list.push({ url: item.file.content, useId: this.userInfo.id, fileId: item.file.fileId, type: STAMP, pid: STAMP, sealSignId: item.id, hasControls: false, sealName: item.sealName, }); }); if (!this.isOwnSign) { this.menuTree[0].children = list; this.mainImagelist.push(...list); } }) .catch(() => { // this.showLoading = false; }); }, getFileServerConfig() { return getFileServerConfig() .then((res) => { if (!res.success || !res.data) { return; } this.fileUrl = res.data.fileServerUrl; }) .catch(() => { // this.showLoading = false; }); }, nullity() { if (!this.currentPage) { this.pageNum = 1; this.currentPage = 1; } }, }, beforeDestroy() { this.removeScrollListeners(); }, }; </script> <style lang="scss" > .drag-sign-seal { .el-tree { overflow: auto; } .el-row { border: 0px solid rgba(190, 197, 208, 1); border-radius: 2px; } .col-left { flex-direction: column; text-align: center; .el-input__inner { width: 100% !important; } } .col-right { display: none !important; border-left: 1px solid rgba(190, 197, 208, 1); height: 100%; padding: 15px; .el-tree { margin-top: 10px; height: calc(100% - 40px); } .custom-tree-node { height: 26px !important; } .el-tree-node__content { height: auto; padding-left: 0 !important; } } /*pdf部分*/ .pCenter { display: block; height: 100%; // overflow: scroll; //overflow-x: auto; //overflow-y: auto; // &::-webkit-scrollbar { // width: 0.12rem !important; // height: 0.12rem !important; // } // &::-webkit-scrollbar-thumb { // border-radius: 6px; // background-color: #dedede; // } // &::-webkit-scrollbar-track { // border-radius: 6px; // background-color: rgba(0, 0, 0, 0); // } } #the-canvas { // margin-top: 10px; border: 0px solid #f0f0f0; } #ele-canvas { border: 0px solid #f0f0f0; } html:fullscreen { background: white; } .elesign { display: flex; flex: 1; flex-direction: column; position: relative; margin: auto; height: 100%; } .page { display: flex; align-items: center; justify-content: center; text-align: center; margin: 0 auto; margin-top: 1%; margin-bottom: 1%; display: flex; .el-button--small span { color: #444 !important; } .el-input__inner { width: 0.6rem; text-align: center; } } .canvas-div-box { width: 100%; height: 100%; overflow-x: auto; overflow-y: auto; -webkit-overflow-scrolling: touch; /* 启用iOS惯性滚动 */ scrollbar-width: none; /* 隐藏Firefox滚动条 */ // &::-webkit-scrollbar { // -webkit-appearance: none; // width: 8px; /* 垂直滚动条宽度 */ // height: 8px; /* 水平滚动条高度 */ // } // &::-webkit-scrollbar-thumb { // background-color: rgba(0, 0, 0, 0.3); // border-radius: 4px; // } // &::-webkit-scrollbar-track { // background-color: rgba(0, 0, 0, 0.05); // } } .canvas-div { width: 100%; margin: 0; overflow-y: hidden; overflow-x: hidden; // height: 100%; position: relative; text-align: left; float: left; transform-origin: 0 0; } #col-left-box { height: 94%; } @keyframes ani-demo-spin { from { transform: rotate(0deg); } 50% { transform: rotate(180deg); } to { transform: rotate(360deg); } } li { list-style-type: none; padding: 10px; } .imgstyle { vertical-align: middle; width: 130px; // border: solid 1px #e8eef2; // background-image: url("./img/rank.png"); background-repeat: no-repeat; } .btn-outline-default { color: #444; background-color: transparent; background-image: none; border: 1px solid #dcdfe6; margin-left: 0.1rem; } .btn-outline-default:hover { color: #444; // border-color: #009994; background-color: #ecfbf8; } .draggable-list { height: calc(100% - 50px); margin-bottom: 5px; &::-webkit-scrollbar { width: 0px; height: 6px; } } .lock { div.lock-icon { position: absolute; bottom: 0px; right: 0px; vertical-align: middle; width: 20px; height: 20px; // background-image: url("./img/lock.png"); background-repeat: no-repeat; cursor: pointer; } img { cursor: not-allowed; background-color: rgba(232, 238, 242, 0.7); /* 半透明遮罩 */ z-index: 10; } } .item { position: relative; } .unlock { & > span { &:hover { background-color: rgba(0, 153, 148, 0.05); cursor: move; } } } .nodrag-tag { cursor: pointer; } .date-item { text-align: center; width: 160px; height: 50px; line-height: 50px; background-color: transparent; &:hover { cursor: move; } } } .landscape .van-tree-select__nav { height: 2rem; } .landscape .van-tree-select__content { height: 2rem; } .landscape .canvas-div { height: 11rem!important; margin: 0!important; } .landscape .col-left-box { padding-right: 0.4rem; overflow: auto; height: 2rem; } .landscape .page { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-align: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; text-align: center; margin-top: -0.01rem; /* margin-top: 0.5%; */ margin-bottom: 0.5%; display: flex; font-size: 0.16rem; position: fixed; /* margin-top: 3rem; */ /* top: 3rem; */ z-index: 3; background: white; width: 100%; /* height: 0.2rem; */ /* top: 0.5rem;*/ .el-button--small span { color: #444 !important; } } </style> 这是个移动端的签字盖章的组件,现在有个问题,当我翻页的时候,上一页的签名会缩小
最新发布
07-05
<template> <div> <virtual-scroll :data="obj.content" :item-size="chartHeight" :visible-count="visibleChartCount" > <template v-slot="{ item, index }"> <div class="chartsList"> <echarts-one-component v-if="item.type === '1'" :obj="item" v-show="isShow" :ref="'child' + index" /> <echarts-two-component v-else-if="item.type === '2'" :obj="item" v-show="isShow" :ref="'child' + index" /> <TableComponent v-else :obj="item" v-show="isShow" :ref="'table' + index" /> </div> </template> </virtual-scroll> </div> </template> <script> const echarts = require('echarts') import EchartsOneComponent from './echartsOneComponent' import EchartsTwoComponent from './echartsTwoComponent' import TableComponent from './tableComponent' import JSZip from 'jszip' import { downLoadPPT, fastApi } from '@/api/ai/aiDashboard' import VirtualScroll from 'vue-virtual-scroll-list' export default { components: { EchartsOneComponent, TableComponent, EchartsTwoComponent, VirtualScroll }, name: 'dashboardOneComponent', props: { obj: { type: Object } }, watch: { obj: { handler(newVal) { //只有当DONE后才渲染 if (newVal.finish === true) { const refObj = this.$refs this.isShow = true for (var i = 0; i < this.obj.content.length; i++) { if (refObj['child' + i]) { refObj['child' + i][0].init(this.obj.content[i]) } if (refObj['table' + i]) { refObj['table' + i][0].init(this.obj.content[i]) } } this.$emit('scrollDown') } }, immediate: true, deep: true } }, data() { return { isShow: false, isDownload:false, chartHeight: 300, // 图表高度,根据实际情况调整 visibleChartCount: 5 // 可视区域显示的图表数量,根据屏幕大小调整 } }, created() { }, mounted() { }, methods: { //生成PPT和ZIP暂时注释 async download() { if (this.isDownload){ return } this.isDownload = true const hide = this.$message.loading('正在下载,大概需要1-2分钟,请耐心等待', 0) try { const zip = new JSZip() const charts = [] // 所有图表实例 const analyse = [] // 所有分析数据 const refObj = this.$refs for (var i = 0; i < this.obj.content.length; i++) { if (this.obj.content[i].echatsName === '新能源') { this.obj.content[i].echatsName = '私家车新能源' } else if (this.obj.content[i].echatsName === '燃油车') { this.obj.content[i].echatsName = '私家车燃油车' } else if (this.obj.content[i].echatsName === '拓展') { this.obj.content[i].echatsName = '业务品质监控扩展(计划)' } if (refObj['child' + i]) { charts.push( { obj:refObj['child' + i][0].getEcharts(), name: refObj['child' + i][0].getEchartTitle() + '@' + this.obj.content[i].echatsName, type: 'echats' }) analyse.push( { question: JSON.stringify(this.obj.content[i]), name: refObj['child' + i][0].getEchartTitle(), direction: this.obj.content[i].type === '1' ? '{direction:若是对分值的分析,则主要分析1分和10分数据,以及1分和10分各个月份的变化。若是对违章次数的分析,则主要分析0违章和-99+空违章占比趋势以及各个月份的变化。}' : '{direction:分析LR和PLR之间的趋势,若是二者差异大,则指出二者差异大的区间,若是二者整体相似,则输出LR和PLR基本靠近,符合预期。}' }) }else{ const tableImage = refObj['table' + i][0].exportImage() charts.push({ obj:tableImage, name: refObj['table' + i][0].getTableTitle() + '@' + this.obj.content[i].echatsName, type: 'table' }) analyse.push({ question: JSON.stringify(this.obj.content[i]), name: refObj['table' + i][0].getTableTitle(), direction: '{direction:若是对高档位(高违章和高占比等)的分析,则不对北京机构进行分析输出,需要分析出占比明显偏高和偏少的数据,以及整体的变化趋势。若是对未开启占比进行分析,则要看一个时间趋势,机构之间没有相对性,看月份的变化,比如有没有新开的、新关的、逐月增加或减少的。}' }) } } const formData = new FormData() // 对接工作流 await fastApi(analyse).then(res => { if (res.state === 'success') { // 获取分析数据数组 const analyseData = res.data // 转换为json文件,交给后端进行解析,用于整合进ppt // const rawJsonBlob = new Blob([JSON.stringify(analyseData)], { type: 'application/json' }) // console.log(rawJsonBlob) // formData.append('analysisData', rawJsonBlob, 'analysis_data.json') for (let i = 0; i < charts.length; i++) { if (charts[i].type === 'table') { zip.file(charts[i].name + '.png', charts[i].obj.split(',')[1], { base64: true }) } else { const imgData = charts[i].obj.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' }) //formData.append('file[]', imgData) zip.file(charts[i].name + '.png', imgData.split(',')[1], { base64: true }) } } zip.generateAsync({ type: 'blob' }).then(content => { formData.append('file', content, 'images.zip') downLoadPPT(formData) .then(res => { const blob = new Blob([res], { type: res.type }) const downloadElement = document.createElement('a') const href = window.URL.createObjectURL(blob) //创建下载的链接 downloadElement.href = href downloadElement.download = 'test.pptx' document.body.appendChild(downloadElement) downloadElement.click() //点击下载 document.body.removeChild(downloadElement) //下载完成移除元素 window.URL.revokeObjectURL(href) //释放blob对象 this.$message.success('下载成功') }).catch((error) => { }) }) } else { console.error('请求失败:', res.message) this.errorMessage = '分析请求失败,请重试' } }).catch(error => { console.error('网络错误:', error) this.errorMessage = '网络请求异常,请检查连接' }) // setTimeout(() => { // downLoadPPT(formData) // .then(res => { // const blob = new Blob([res], {type: res.type}) // const downloadElement = document.createElement('a') // const href = window.URL.createObjectURL(blob) //创建下载的链接 // downloadElement.href = href // downloadElement.download = 'test.pptx' // document.body.appendChild(downloadElement) // downloadElement.click() //点击下载 // document.body.removeChild(downloadElement) //下载完成移除元素 // window.URL.revokeObjectURL(href) //释放blob对象 // this.$message.success('下载成功') // }).catch((error) => { // }) // }, 15000) } catch (err) { this.$message.warn('下载PPT异常请稍后再试') } finally { this.isDownload = false this.$message.destroy(hide) } } /*download() { const zip = new JSZip() const charts = [] // 所有图表实例 const refObj = this.$refs for (var i = 0; i < this.obj.content.length; i++) { if (refObj['child' + i]) { charts.push(refObj['child' + i][0].getEcharts()) } } const pdf = new jsPDF('landscape', 'pt', 'a4') // 横向A4纸‌:ml-citation{ref="2,7" data="citationList"} const pageHeight = pdf.internal.pageSize.getHeight() let currentY = 0 // 当前绘制高度 for (let i = 0; i < charts.length; i++) { const imgData = charts[i].getDataURL({type: 'png', pixelRatio: 2,backgroundColor: '#fff'}) const imgProps = pdf.getImageProperties(imgData) const imgWidth = pdf.internal.pageSize.getWidth() const imgHeight = (imgProps.height * imgWidth) / imgProps.width // 分页判断 if (currentY + imgHeight > pageHeight) { pdf.addPage() currentY = 0 } pdf.addImage(imgData, 'PNG', 0, currentY, imgWidth, imgHeight) currentY += imgHeight + 20 //zip.file(`chart${i + 1}.png`, imgData.split(',')[1],{ base64: true }) } pdf.save('chart.pdf') //pdf.save('chart.pdf') /!*zip.generateAsync({type: 'blob'}).then(content => { saveAs(content, 'charts.zip') })*!/ }*/ } } </script> <style scoped> .chartsList{ display: flex; justify-content: center; text-align: center; margin-bottom: 25px; height: 300px;/* 根据实际高度设置 */ overflow: hidden; } </style>
06-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值