使用innerhtml时报"object error"错误

今天在使用document.getElementById(“**id”).innerHTML = 'aaaaa',始终报"object error"错误,经过一下午折腾终于解决了,记录一下!
由于程序从想在Ext.Panel中包含页面html中的一个表单From,于是有了如下的配置:

new Ext.Panel({
contentEl:'content',//.........
id :'content2'
})


当程序中启用contentEl:'content'将表单From插入panel后,使用innerHTML修改表单中的元素内容就会一直报"object error";当注释掉contentEl:'content'后,错误就消失;反复试验终于发现是innerHTML 修改contentEl内容中的FROM中的元素时出现了此问题!为什么出这个问题不得知,还望大家赐教!
条件组合MySQL数据库密码是220603,我提供一个excel文档,我可以将这个文档进行导入到我的HTML页面里面的导入按钮,端口号为5008 导入功能:店铺管理(添加,修改,删除店铺的功能),通过输入店铺码和店铺名称来添加,添加完成之后会有一个进行导入店铺数据功能,这个就通过一个选择文件(如excel和csv),我认为在MySQL数据库里面创建表太麻烦了,只想创建一个数据库名字为shop_order,然后根据导入文件的第一行的所有表名字作为关键字创建所对应的MySQL表放在shop——oeder下面(如将Paid Time自动识别为DATETIME类型),切记这个关键字就是导入文档表的第一行,并且下面都是该关键字所对应的所有数据,由于我所做的系统需要导入三个文档,所以要设置三个导入数据的按钮,分别为All order,in come,creater order这三个表所对应了所有订单数据,收入订单数据和达人订单数据(也就是通过达人带货卖出去的订单)。 查询功能:在导入需要的数据之后,我们需要进行数据查询,目前先通过店铺码和店铺名称查询店铺,查询成功后进入店铺,这个时候我们需要再查询店铺的数据,这个时候就需要通过查询我们目前需要的数据,有最开始得到的关键字:Order ID,Paid Time(付款时间),还要给这个数据排列,也就是在这两个查询数据前面还需要建立一个Shop ID作为编号如01,02,03等等,查询后显示的完整数据的是[Shop ID:Order ID,Paid Time],实现上面功能还不够,我希望还可以增加一个时间查询也就是可以输入某个时间段到某个时间段来进行查询这段时间所有订单的数据,列如Start time[年月日]——End timr[年月日],这个时间查询是通过最开始得到的关键字里面的Paid Time所对应的时间数据("10/07/2025 10:24:31 " )来决定,因为不是每个订单号Order ID都会被进行付款(paid_time),我们只需要提前这个付款时间不需要其他时间,所以只需要查询有paid_time的Order ID并且提取出来即可,若没有paid_time数据就直接跳过放弃,继续查询有paid_time的数据订单即可,根据以上要求给出这个项目的开发教程,要求保姆级教程,再说一遍,在里面必须有可以导入excel和csv文档的功能,并且是点击导入数据就可以在我的电脑里面查找选择文档。要注意下面几个问题,第一:日期时间列识别问题:系统错误地将非日期列(如 Order_Status、Quantity)识别为日期列,日期转换失败率高(如 Created_Time 只有33.4%的转换成功率),错误提示:列 XXX 保持原样: 日期格式识别率低 (0.0%)原因是:仅通过列名关键词(如包含"time")判断是否为日期列,缺乏数据内容验证,未排除明确非日期列的字段(如状态、数量、金额等),未处理多种日期格式(如 10/07/2023 10:24:31 和 2023-07-10T15:45:00),一定要增强列识别逻辑,还有多格式日期解析和分阶段验证。 第二:数据类型推断错误:避免数值列(如 Quantity)被误识别为日期,数据库插入时报错:Incorrect datetime value: '579567909575820736' for column 'Order ID',原因是因为类型推断仅基于列名,未结合数据内容,未处理大整数和特殊格式数字。所以要设置一个优先级调整和安全转换。 第三:数据库交互问题,特别是RuntimeError: The session is unavailable because no secret key was set,事务回滚失败导致表残留,数据类型不匹配(如DATETIME和VARCHAR冲突)原因是Flask会话密钥未配置未正确处理MySQL错误代码(如1292=日期格式错误)和表结构创建与数据插入逻辑分离。所以要做到基础配置和事务管理和原子性操作这三个方面。 第四:前端模板渲染问题: 问题表现: jinja2.exceptions.TemplateNotFound jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'id' JavaScript中 const report = {{ date_report|tojson|safe }} 报错 根本原因: 模板文件未放在正确目录(templates/),变量未定义或为None时直接调用方法,未处理JSON序列化中的None值,解决方案:设置目录结构规范和安全变量访问以及默认值处理。 第五: 文件上传与数据处理问题: 问题表现: 上传的Excel/CSV文件解析失败 临时文件未清理 列名含空格导致SQL错误 根本原因: 未验证文件内容格式 未处理文件编码问题(如UTF-8 vs GBK) 未规范化列名(如 Paid Time → Paid_Time) 解决方案:设置文件预处理和资源清理和编码指定。 第六:分页与性能问题: 问题表现: 大数据量查询时内存溢出 分页逻辑错误(LIMIT/OFFSET计算) 解决方案:设置流式查询和分页优化。 最后做一些优化比如:系统架构改进建议 日志监控: python代码: app.logger.addHandler(logging.FileHandler('import_errors.log')) 配置分离: python代码: class DevelopmentConfig(Config): DEBUG = True UPLOAD_FOLDER = '/tmp/uploads' 单元测试: python代码: def test_datetime_detection(): assert is_potential_datetime_column("Paid_Time", pd.Series(["2023-01-01"])) is True 根据上面所有要求和问题还有解决方法制定一个Python电商数据分析系统教程,要求保姆级教程!!!请仔细分析我给出的所有问题并且一一解决生成一个完整教程,要求准确并且代码质量高可以运行
07-15
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="shortcut icon" href="Sources/logo.png"> <meta property="og:image" content="https://digimktgsolution.com/Housing-Authority-AI-Photo/Sources/logo.png"> <meta property="og:image:type" content="image/png"> <meta property="og:image:width" content="308"> <meta property="og:image:height" content="308"> <meta property="og:title" content="Housing-Authority-AI-Photo"> <meta property="og:description" content="Housing-Authority-AI-Photo"> <title>Housing-Authority-AI-Photo</title> <!-- 引入二维码生成库 --> <script src="https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs@gh-pages/qrcode.min.js"></script> <style> @keyframes heartbeat { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } .BG { position: relative; top: 0; left: 0; width: 1920px; height: 950px; } /* 图片网格样式 */ #imageGrid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; } .image-container { aspect-ratio: 3/2; overflow: hidden; border-radius: 6px; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; position: relative; cursor: pointer; /* 添加指针样式 */ transition: all 0.3s ease; } .image-container:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); } .image-container img { max-width: 100%; max-height: 100%; object-fit: contain; transition: transform 0.4s ease; } /* 加载动画 */ .image-container::before { content: ""; position: absolute; width: 30px; height: 30px; border: 3px solid rgba(0,0,0,0.1); border-top-color: #3498db; border-radius: 50%; animation: spin 1s linear infinite; opacity: 0; transition: opacity 0.3s; } .image-container.loading::before { opacity: 1; } @keyframes spin { to { transform: rotate(360deg); } } body { margin: 0; font-family: Arial, Helvetica, sans-serif; height: 100%; overflow: hidden; background-color: #e6939d; } .content { position: absolute; top: 14%; scale: 0.9; left: 3%; height: 810px; overflow-y: scroll; margin-top:6%; } .Preview { position: absolute; top: 28%; left: 12%; width: 39%; height: auto; border: 5px solid white; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); margin-top: 13%; } /* 页面切换动画 */ .page { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transition: opacity 0.5s ease; } /* 返回按钮 */ .back-btn { position: absolute; top: 5%; left: 5%; color: white; border: none; padding: 10px 20px; border-radius: 50px; cursor: pointer; font-size: 16px; font-weight: bold; z-index: 100; margin-top: 40%; width: 10%; margin-left: -4%; } /* 二维码容器样式 */ #qrcodeContainer { position: absolute; top: 28%; right: 16%; width: 16%; height: auto; background: white; padding: 10px; border-radius: 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.2); margin-top: 11%; } #qrcode { width: 100%; height: auto; } .qrcode-label { text-align: center; margin-top: 10px; font-size: 14px; font-weight: bold; color: #333; } </style> </head> <body> <div style="position: relative; left: 0; top: 0;"> <div id="HomePage" class="page"> <img src="Sources/GalleryBG.png" class="BG" width="1080" height="1920"> <div id="content" class="content"> <div id="imageGrid" class="imageGrid"></div> </div> </div> <div id="ScanPage" class="page" style="display: none;"> <img src="Sources/ScanBG.jpg" class="BG" width="1080" height="1920"> <div id="previewCon"> <img src="" class="Preview" id="Preview"> <img src="Sources/GallerybackBtn.png" id="back-btn" class="back-btn" onclick="showHomePage()"> <!-- 二维码容器 --> <div id="qrcodeContainer"> <div id="qrcode"></div> </div> </div> </div> </div> <script> // 全局变量存储当前选择的图片URL let selectedImageUrl = ""; let currentQRCode = null; // 存储当前二维码实例 // 页面切换函数 function showHomePage() { document.getElementById('HomePage').style.display = 'block'; document.getElementById('ScanPage').style.display = 'none'; } function showScanPage(imgUrl) { selectedImageUrl = imgUrl; document.getElementById('Preview').src = imgUrl; document.getElementById('HomePage').style.display = 'none'; document.getElementById('ScanPage').style.display = 'block'; // 生成二维码 generateQRCode(imgUrl); } // 生成二维码函数 function generateQRCode(imgUrl) { // 获取图片文件名(不含.jpg扩展名) const fileName = imgUrl.substring(imgUrl.lastIndexOf('/') + 1).replace('.jpg', ''); // 构造二维码内容 const qrContent = `https://digimktgsolution.com/Housing-Authority-AI-Photo/download.html?id=${fileName}`; // 清除之前的二维码 const qrcodeDiv = document.getElementById('qrcode'); qrcodeDiv.innerHTML = ''; // 销毁旧的二维码实例(如果存在) if (currentQRCode) { currentQRCode.clear(); } // 创建新的二维码 currentQRCode = new QRCode(qrcodeDiv, { text: qrContent, width: 300, height: 300, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); } // 示例图片URL数组 var imageUrls = []; let da = {} // 获取图片数据 fetch("GetImage.php", { method: "POST", headers: { "Content-Type": "application/json; charset = utf-8" }, body: JSON.stringify(da) }).then(function (response) { return response.text(); }).then(function (data) { imageUrls = data.split('*'); console.log('Loaded images:', imageUrls.length - 1); const container = document.getElementById('imageGrid'); // 逆序数组(最后一张在最前) [...imageUrls].reverse().forEach(url => { if (url) { const imgContainer = document.createElement('div'); imgContainer.className = 'image-container loading'; const img = new Image(); img.src = url; img.alt = "Gallery image"; img.loading = "lazy"; // 添加点击事件监听 imgContainer.addEventListener('click', function() { showScanPage(url); }); img.onload = function () { imgContainer.classList.remove('loading'); if (img.naturalHeight > img.naturalWidth) { imgContainer.classList.add('portrait'); } }; img.onerror = function () { imgContainer.classList.remove('loading'); console.error('Image load failed:', url); img.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24"><rect width="24" height="24" fill="%23f0f0f0"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="monospace" font-size="10px">加載失敗</text></svg>'; }; imgContainer.appendChild(img); container.appendChild(imgContainer); } }); }).catch(function(error) { console.error('Fetch error:', error); }); </script> </body> </html> js使用以上代碼,當開啓”ScanPage“後進行60秒倒數,倒數完畢則回到”HomePage“,點擊返回按鈕也回到”HomePage“,當再次點擊其他圖片開啓”ScanPage“後重新倒數60秒。倒數顯示於”previewCon“裏面的二維碼div下面
最新发布
10-31
为什么高亮坐标无法清空 <template> <Teleport to="body"> <div v-if="visible" class="pdf-mask" @mousedown="startDrag"> <div ref="modalRef" class="pdf-modal" :style="modalStyle"> <!-- PDF 头部:标题 + 操作按钮 --> <div class="pdf-header"> <span class="title">报关资料</span> <div class="tools"> <el-button size="small" @click="zoomIn">放大</el-button> <el-button size="small" @click="zoomOut">缩小</el-button> <el-button size="small" @click="close">关闭</el-button> </div> </div> <!-- PDF 标签页 + 内容区域 --> <el-tabs v-model="activeTab" class="pdf-tabs" @tab-click="handleTabClick"> <el-tab-pane v-for="tab in tabs" :key="tab.id" :label="tab.name" :name="tab.id.toString()"> <div class="pdf-body" :ref="(el) => el && scrollRefMap.set(tab.id, el as HTMLDivElement)" @wheel="(e) => onWheel(e, tab.id)" @mousedown="(e) => onPdfMouseDown(e, tab.id)"> <div :ref="(el) => el && setPdfContainer(tab.id, el as HTMLDivElement)" /> </div> </el-tab-pane> </el-tabs> </div> </div> </Teleport> </template> <script setup lang="ts"> import { ref, reactive, watch, nextTick, onUnmounted } from 'vue' import type { CSSProperties } from 'vue' import * as pdfjsLib from 'pdfjs-dist' import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker&url' import type { PDFDocumentLoadingTask, PDFDocumentProxy, PDFPageProxy, RenderParameters } from 'pdfjs-dist/types/src/display/api' import type { PageViewport } from 'pdfjs-dist' import { ElButton, ElTabs, ElTabPane } from 'element-plus' import type { TabsPaneContext } from 'element-plus' // PDF.js 初始化 pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker /* ---------------- Props & Emits ---------------- */ const props = defineProps<{ visible: boolean url?: string highlight?: { x0: number; y0: number; x1: number; y1: number } | null }>() const emit = defineEmits<{ (e: 'update:visible', value: boolean): void (e: 'tabschange', tabId: string | number): void }>() /* ---------------- 状态 ---------------- */ const tabs = ref<Array<{ id: string | number; name: string; url: string }>>([]) const activeTab = ref<string>('') const pdfContainerMap = new Map<string | number, HTMLDivElement>() const scrollRefMap = new Map<string | number, HTMLDivElement>() const tabScale = new Map<string | number, number>() const tabBaseViewport = new Map<string | number, PageViewport>() const modalRef = ref<HTMLDivElement | null>(null) let abortController: AbortController | null = null let currentScrollContainer: HTMLDivElement | null = null /* ---------------- 弹窗位置 ---------------- */ const modalStyle = reactive<{ left: string top: string width: string height: string cursor: CSSProperties['cursor'] }>({ left: '0px', top: '0px', width: '800px', height: '600px', cursor: 'default' }) let dragStart: { x: number; y: number } | null = null /* ---------------- 标签页初始化 ---------------- */ async function openWithTabs( list: Array<{ code: string; name: string; url: string }> ) { tabs.value = [] pdfContainerMap.clear() scrollRefMap.clear() tabScale.clear() tabBaseViewport.clear() clearHighlight(); if (abortController) { abortController.abort() abortController = null } tabs.value = list .filter(item => item.name !== 'all_files') .map(item => ({ id: item.code, name: item.name, url: item.url })) const firstTabId = tabs.value[0]?.id.toString() ?? '' activeTab.value = firstTabId emit('update:visible', true) await nextTick() const firstTab = tabs.value[0] if (firstTab) { const container = pdfContainerMap.get(firstTab.id) if (container) await renderSinglePDF(firstTab.url, container, firstTab.id) } // 居中 const { innerWidth, innerHeight } = window modalStyle.left = `${(innerWidth - 800) / 2}px` modalStyle.top = `${(innerHeight - 600) / 2}px` } function setPdfContainer(tabId: string | number, el: HTMLDivElement) { pdfContainerMap.set(tabId, el) } /* ---------------- PDF 渲染 ---------------- */ async function renderSinglePDF( pdfUrl: string, container: HTMLDivElement, currentTabId: string | number ) { container.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) if (activeTab.value !== currentTabId.toString()) return if (!(container instanceof HTMLDivElement)) return container.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) if (abortController) { abortController.abort() } abortController = new AbortController() container.innerHTML = '' const scrollContainer = scrollRefMap.get(currentTabId) if (!scrollContainer) return try { const pdfLoadingTask: PDFDocumentLoadingTask = pdfjsLib.getDocument({ url: pdfUrl, signal: abortController.signal } as any) as PDFDocumentLoadingTask const pdfDoc: PDFDocumentProxy = await pdfLoadingTask.promise const pdfPage: PDFPageProxy = await pdfDoc.getPage(1) if (activeTab.value !== currentTabId.toString()) return const baseViewport = pdfPage.getViewport({ scale: 1 }) tabBaseViewport.set(currentTabId, baseViewport) const currentScale = tabScale.get(currentTabId) ?? 1 const renderScale = currentScale * (800 / baseViewport.width) const renderViewport = pdfPage.getViewport({ scale: renderScale }) const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) { container.innerHTML = '<div class="pdf-error">Canvas 初始化失败</div>' return } canvas.width = renderViewport.width canvas.height = renderViewport.height canvas.dataset.scale = String(currentScale) container.appendChild(canvas) const renderParams: RenderParameters = { canvasContext: ctx, viewport: renderViewport, signal: abortController } as any await pdfPage.render(renderParams).promise if (activeTab.value !== currentTabId.toString()) { container.innerHTML = '' return } // 关键修改:不再给滚动容器写死宽高 // scrollContainer.style.width = `${renderViewport.width}px` // scrollContainer.style.height = `${renderViewport.height}px` if (props.highlight) drawHighlight(props.highlight, currentTabId) } catch (error) { if ((error as Error).name !== 'AbortError') { console.error('PDF 加载失败:', error) container.innerHTML = '<div class="pdf-error">PDF 加载失败,请重试</div>' } } } /* ---------------- 标签页切换 & 缩放 ---------------- */ function handleTabClick(tab: TabsPaneContext) { clearHighlight(); emit('tabschange', tab.paneName) } watch(activeTab, (newTabIdStr) => { clearHighlight(); const tab = tabs.value.find(t => t.id.toString() === newTabIdStr) if (!tab) return const container = pdfContainerMap.get(tab.id) if (container) renderSinglePDF(tab.url, container, tab.id) }) const zoomIn = () => { const id = tabs.value.find(t => t.id.toString() === activeTab.value)?.id if (!id) return tabScale.set(id, Math.min((tabScale.get(id) ?? 1) + 0.2, 3)) rerenderActiveTab() } const zoomOut = () => { const id = tabs.value.find(t => t.id.toString() === activeTab.value)?.id if (!id) return tabScale.set(id, Math.max((tabScale.get(id) ?? 1) - 0.2, 0.5)) rerenderActiveTab() } async function rerenderActiveTab() { const tab = tabs.value.find(t => t.id.toString() === activeTab.value) if (!tab) return const container = pdfContainerMap.get(tab.id) if (container) await renderSinglePDF(tab.url, container, tab.id) } /* ---------------- 弹窗拖拽 ---------------- */ function startDrag(e: MouseEvent) { const target = e.target as HTMLElement if (!target.classList.contains('pdf-mask') && !target.closest('.pdf-header')) return if (!modalRef.value) return dragStart = { x: e.clientX, y: e.clientY } modalStyle.cursor = 'grabbing' document.addEventListener('mousemove', onDrag) document.addEventListener('mouseup', stopDrag) e.preventDefault() } function onDrag(e: MouseEvent) { if (!dragStart) return const dx = e.clientX - dragStart.x const dy = e.clientY - dragStart.y modalStyle.left = `${parseFloat(modalStyle.left) + dx}px` modalStyle.top = `${parseFloat(modalStyle.top) + dy}px` dragStart = { x: e.clientX, y: e.clientY } } function stopDrag() { dragStart = null modalStyle.cursor = 'default' document.removeEventListener('mousemove', onDrag) document.removeEventListener('mouseup', stopDrag) } /* ---------------- PDF 内容拖拽 ---------------- */ let isPdfDragging = false let pdfDragStart = { x: 0, y: 0 } let scrollStart = { left: 0, top: 0 } function onPdfMouseDown(e: MouseEvent, tabId: string | number) { const sc = scrollRefMap.get(tabId) if (!sc) return const scale = tabScale.get(tabId) ?? 1 if (scale <= 1) return isPdfDragging = true pdfDragStart = { x: e.clientX, y: e.clientY } scrollStart = { left: sc.scrollLeft, top: sc.scrollTop } currentScrollContainer = sc document.addEventListener('mousemove', onPdfMouseMove) document.addEventListener('mouseup', onPdfMouseUp) e.preventDefault() } function onPdfMouseMove(e: MouseEvent) { if (!isPdfDragging || !currentScrollContainer) return const dx = e.clientX - pdfDragStart.x const dy = e.clientY - pdfDragStart.y currentScrollContainer.scrollTo({ left: scrollStart.left - dx, top: scrollStart.top - dy, behavior: 'auto' }) } function onPdfMouseUp() { isPdfDragging = false currentScrollContainer = null document.removeEventListener('mousemove', onPdfMouseMove) document.removeEventListener('mouseup', onPdfMouseUp) } /* ---------------- 滚轮事件 ---------------- */ function onWheel(e: WheelEvent, tabId: string | number) { e.stopPropagation() const sc = scrollRefMap.get(tabId) if (sc) { const scale = tabScale.get(tabId) ?? 1 sc.scrollLeft -= e.deltaX * scale sc.scrollTop -= e.deltaY * scale e.preventDefault() } } /* ---------------- 高亮区域绘制 ---------------- */ function drawHighlight( rect: { x0: number; y0: number; x1: number; y1: number }, tabId?: string | number ) { const id = tabId ?? tabs.value.find(t => t.id.toString() === activeTab.value)?.id if (!id) return const container = pdfContainerMap.get(id) const sc = scrollRefMap.get(id) if (!container || !sc) return container.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) const canvas = container.querySelector('canvas') const baseViewport = tabBaseViewport.get(id) if (!canvas || !baseViewport) return const scale = parseFloat(canvas.dataset.scale || '1') const scaleX = canvas.width / baseViewport.width const scaleY = canvas.height / baseViewport.height const highlightEl = document.createElement('div') highlightEl.className = 'pdf-highlight' Object.assign(highlightEl.style, { position: 'absolute', left: `${rect.x0 * scaleX}px`, top: `${rect.y0 * scaleY}px`, width: `${(rect.x1 - rect.x0) * scaleX}px`, height: `${(rect.y1 - rect.y0) * scaleY}px`, background: 'rgba(0, 255, 0, 0.4)', border: '2px dashed #ff4d4f', pointerEvents: 'none', zIndex: 9999 }) container.style.position = 'relative' container.appendChild(highlightEl) nextTick(() => { const cw = sc.clientWidth const ch = sc.clientHeight sc.scrollTo({ left: Math.max(0, parseFloat(highlightEl.style.left) - cw / 3), top: Math.max(0, parseFloat(highlightEl.style.top) - ch / 3), behavior: 'smooth' }) }) } function drawHighlightByCoords(rect: { x0: number; y0: number; x1: number; y1: number }) { drawHighlight(rect) } // 清空所有高亮元素 function clearHighlight() { scrollRefMap.forEach((sc) => { sc.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) }) pdfContainerMap.forEach((container) => { container.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) }) } /* ---------------- 监听 Props ---------------- */ watch( () => props.visible, async (v) => { if (!v) { tabScale.clear() if (abortController) { abortController.abort() abortController = null } currentScrollContainer = null clearHighlight(); return } clearHighlight(); await nextTick() const { innerWidth, innerHeight } = window modalStyle.left = `${(innerWidth - 800) / 2}px` modalStyle.top = `${(innerHeight - 600) / 2}px` }, { immediate: true } ) watch( () => props.highlight, (hl) => { if (hl) { drawHighlight(hl) } else { clearHighlight() // 关键:当 highlight 为 null 时清除高亮 } }, { immediate: false, deep: true } ) /* ---------------- 关闭 ---------------- */ function close() { if (abortController) { abortController.abort() abortController = null } currentScrollContainer = null clearHighlight(); emit('update:visible', false) } /* ---------------- 清理 ---------------- */ onUnmounted(() => { if (abortController) abortController.abort() document.removeEventListener('mousemove', onDrag) document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mousemove', onPdfMouseMove) document.removeEventListener('mouseup', onPdfMouseUp) pdfContainerMap.clear() scrollRefMap.clear() tabScale.clear() tabBaseViewport.clear() currentScrollContainer = null }) /* ---------------- 暴露 ---------------- */ defineExpose({ openWithTabs, drawHighlight: drawHighlightByCoords }) </script> <style scoped> .pdf-mask { position: fixed; inset: 0; z-index: 9999; pointer-events: none; } .pdf-modal { position: absolute; background: #ffffff; border: 1px solid #e4e7ed; border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; pointer-events: auto; min-width: 400px; min-height: 300px; resize: both; overflow: hidden; } .pdf-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: #f5f7fa; cursor: grab; border-bottom: 1px solid #e4e7ed; user-select: none; } .pdf-header:active { cursor: grabbing; } .pdf-header .title { font-size: 16px; font-weight: 500; color: #1f2937; } .pdf-header .tools { display: flex; gap: 8px; } .pdf-tabs { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .pdf-body { flex: 1; overflow: auto; position: relative; } .pdf-body canvas { pointer-events: none; display: block; } .pdf-highlight { box-sizing: border-box; } .pdf-tabs .el-tabs__content { flex: 1; overflow: hidden; padding: 0 !important; } .pdf-tabs .el-tab-pane { height: 100%; display: flex; flex-direction: column; } .pdf-error { padding: 20px; text-align: center; color: #f56c6c; } </style>
08-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值