提交 87fd5b20 authored 作者: 龙菲's avatar 龙菲

feat: 增加pc端的全文搜索和跳转

上级 e979a9b4
......@@ -38,3 +38,16 @@ export function getDocumentBookmarks(id) {
method: 'get'
});
}
/**
* 全文搜索
* @returns {Promise} 返回文档详情数据
*/
export function searchText(params) {
return request({
url: `/search/text`,
method: 'get',
params
});
}
......@@ -13,12 +13,12 @@ declare module 'vue' {
BookReader: typeof import('./components/BookReader/index.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElRow: typeof import('element-plus/es')['ElRow']
ElText: typeof import('element-plus/es')['ElText']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
FileUpload: typeof import('./components/FileUpload.vue')['default']
GuideMobile: typeof import('./components/GuideMobile/index.vue')['default']
......@@ -28,10 +28,10 @@ declare module 'vue' {
IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default']
IconSupport: typeof import('./components/icons/IconSupport.vue')['default']
IconTooling: typeof import('./components/icons/IconTooling.vue')['default']
IndexFirst: typeof import('./components/PdfViewer/index-first.vue')['default']
PdfViewer: typeof import('./components/PdfViewer/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchBar: typeof import('./components/SearchBar/index.vue')['default']
SvgIcon: typeof import('./components/SvgIcon/index.vue')['default']
Test: typeof import('./components/test/index.vue')['default']
VanCell: typeof import('vant/es')['Cell']
......
......@@ -147,7 +147,6 @@
<svg-icon name="to-end" :size="30" @click="toEnd"></svg-icon>
</el-tooltip>
</el-col>
</el-row>
</div>
</template>
......@@ -183,13 +182,13 @@ const calculatePageDimensions = (viewportWidth, viewportHeight, zoom = 1.0) => {
// 计算基于宽度的最大尺寸
const maxWidth = viewportWidth / 2 - 50;
const maxHeightByWidth = maxWidth * PDF_RATIO; // 高度 = 宽度 × 1.414
// 计算基于高度的最大尺寸
const maxHeight = viewportHeight - 100;
const maxWidthByHeight = maxHeight / PDF_RATIO; // 宽度 = 高度 ÷ 1.414
let baseWidth, baseHeight;
// 选择能撑满一边的方案
if (maxHeightByWidth <= maxHeight) {
// 宽度能撑满,高度也满足
......@@ -200,7 +199,7 @@ const calculatePageDimensions = (viewportWidth, viewportHeight, zoom = 1.0) => {
baseWidth = maxWidthByHeight;
baseHeight = maxHeight;
}
// 确保不超过移动端的限制
if (isMobile.value) {
baseWidth = Math.min(baseWidth, viewportWidth - 20);
......@@ -215,7 +214,7 @@ const calculatePageDimensions = (viewportWidth, viewportHeight, zoom = 1.0) => {
baseWidth,
baseHeight,
scaledWidth,
scaledHeight
scaledHeight,
};
} catch (error) {
console.error("Error calculating page dimensions:", error);
......@@ -227,23 +226,22 @@ const calculatePageDimensions = (viewportWidth, viewportHeight, zoom = 1.0) => {
const updatePageDimensions = (dimensions, $magazine) => {
// 直接更新 turn.js 尺寸
if (isMobile.value) {
if (isMobile.value) {
$magazine.turn("size", dimensions.scaledWidth, dimensions.scaledHeight);
} else {
$magazine.turn("size", dimensions.scaledWidth * 2, dimensions.scaledHeight);
}
// 逐页强制刷新 flip wrapper
const data = $magazine.data();
for (let page in data.pages) {
if (data.pages.hasOwnProperty(page)) {
data.pages[page].flip('resize', true);
data.pages[page].flip("resize", true);
}
}
// 更新 z-index 和交互区域
$magazine.turn('update');
$magazine.turn("update");
};
// 更新视口样式
......@@ -268,31 +266,34 @@ const restoreLoadedImages = (visiblePages, $magazine) => {
};
// 新增:封装 PDF viewport 尺寸计算
const calculatePdfViewportScale = (pdfPage, containerWidth, containerHeight) => {
const calculatePdfViewportScale = (
pdfPage,
containerWidth,
containerHeight
) => {
try {
const originalViewport = pdfPage.getViewport({ scale: 1.0 });
const viewportWidth = originalViewport.viewBox[2];
const viewportHeight = originalViewport.viewBox[3];
const scaleX = containerWidth / viewportWidth;
const scaleY = containerHeight / viewportHeight;
const scale = Math.min(scaleX, scaleY); // 保持比例
// 验证 scale 值
if (isNaN(scale) || scale <= 0) {
return null;
}
// 计算补偿后的 scale
const cssUnits = pdfjsViewer.CSS_UNITS || (96.0 / 72.0);
const cssUnits = pdfjsViewer.CSS_UNITS || 96.0 / 72.0;
const compensatedScale = scale / cssUnits;
return {
scale,
compensatedScale,
viewportWidth,
viewportHeight
viewportHeight,
};
} catch (error) {
console.error("计算 PDF viewport scale 失败:", error);
......@@ -319,22 +320,32 @@ const getVisiblePageIndexes = (currentPage, totalPages, isMobile) => {
// 新增:封装获取可见页面信息(用于图片恢复)
const getVisiblePageInfo = ($magazine, processedPages) => {
if (!processedPages.value.length) return [];
const currentPage = $magazine.turn("page");
const visibleIndexes = getVisiblePageIndexes(currentPage, processedPages.value.length, isMobile.value);
return visibleIndexes.map(index => ({
const visibleIndexes = getVisiblePageIndexes(
currentPage,
processedPages.value.length,
isMobile.value
);
return visibleIndexes.map((index) => ({
page: index + 1,
isLoaded: processedPages.value[index]?.isLoaded || false
isLoaded: processedPages.value[index]?.isLoaded || false,
}));
};
// 新增:封装创建 PDFPageView
const createPdfPageView = (pageDiv, pageNum, compensatedScale, newViewport, eventBus) => {
const createPdfPageView = (
pageDiv,
pageNum,
compensatedScale,
newViewport,
eventBus
) => {
if (!pdfjsViewer) {
throw new Error('PDF.js viewer not loaded');
throw new Error("PDF.js viewer not loaded");
}
return new pdfjsViewer.PDFPageView({
container: pageDiv,
id: pageNum - 1,
......@@ -348,7 +359,7 @@ const createPdfPageView = (pageDiv, pageNum, compensatedScale, newViewport, even
enableXfa: false,
l10n: null,
textLayerFactory: new pdfjsViewer.DefaultTextLayerFactory(),
useOnlyCssZoom: false
useOnlyCssZoom: false,
});
};
......@@ -360,7 +371,7 @@ const waitForContainerSize = async (pageContainer, pageNum) => {
// 如果容器尺寸为0,等待turn.js完全初始化
if (containerWidth === 0 || containerHeight === 0) {
console.log(`页面${pageNum}容器尺寸为0,等待turn.js初始化...`);
await new Promise((resolve) => {
const checkSize = () => {
const width = pageContainer.clientWidth;
......@@ -391,32 +402,32 @@ const loadPdfJs = async () => {
if (window.pdfjsLib && window.pdfjsViewer) {
pdfjsLib = window.pdfjsLib;
pdfjsViewer = window.pdfjsViewer;
console.log('PDF.js already loaded from script tags');
console.log("PDF.js already loaded from script tags");
return;
}
// 如果全局变量不存在,则动态创建script标签加载
await loadScript('/pdfjs-dist/build/pdf.js');
await loadScript('/pdfjs-dist/web/pdf_viewer.js');
await loadScript("/pdfjs-dist/build/pdf.js");
await loadScript("/pdfjs-dist/web/pdf_viewer.js");
// 等待一小段时间确保脚本加载完成
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise((resolve) => setTimeout(resolve, 100));
// 从全局变量获取
if (window.pdfjsLib && window.pdfjsViewer) {
pdfjsLib = window.pdfjsLib;
pdfjsViewer = window.pdfjsViewer;
// 设置PDF.js的worker路径
// if (pdfjsLib.GlobalWorkerOptions) {
// pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs-dist/web/pdf.worker.js';
// }
console.log('PDF.js loaded successfully via script tags');
console.log("PDF.js loaded successfully via script tags");
} else {
throw new Error('PDF.js failed to load');
throw new Error("PDF.js failed to load");
}
} catch (error) {
console.error('Failed to load PDF.js:', error);
console.error("Failed to load PDF.js:", error);
}
};
......@@ -428,8 +439,8 @@ const loadScript = (src) => {
resolve();
return;
}
const script = document.createElement('script');
const script = document.createElement("script");
script.src = src;
script.onload = resolve;
script.onerror = reject;
......@@ -464,7 +475,6 @@ const props = defineProps({
type: Boolean,
default: false,
},
});
const magazine = ref(null);
......@@ -502,7 +512,8 @@ const textLayerEnabled = ref(props.enableTextLayer); // 文字层开关状态
// SVG渲染器开关状态
const svgRendererEnabled = ref(
localStorage.getItem("bookReaderSvgRenderer") === "true" || props.enableSvgRenderer
localStorage.getItem("bookReaderSvgRenderer") === "true" ||
props.enableSvgRenderer
);
// 文本层可见性状态(用于调试)
......@@ -639,7 +650,9 @@ const loadVisiblePages = async (currentPageNum) => {
const endPage = Math.min(totalPages, currentPageNum + 1);
const highPriority = [currentPageNum, currentPageNum + 1]; // 优先加载当前和下一页
console.log(`预加载页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`);
console.log(
`预加载页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`
);
try {
// 静默加载,不显示loading状态
......@@ -671,13 +684,15 @@ const renderVisiblePdfPages = async (currentPageNum) => {
// 使用processedPages的长度而不是turn.js的pages值,因为turn.js可能还没有完全初始化
const totalPages = processedPages.value.length;
// 渲染当前页、上一页、下一页
const startPage = Math.max(1, currentPageNum - 1);
const endPage = Math.min(totalPages, currentPageNum + 1);
const highPriority = [currentPageNum, currentPageNum + 1]; // 优先渲染当前和下一页
console.log(`渲染PDF页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`);
console.log(
`渲染PDF页面: ${currentPageNum}, 范围: ${startPage}-${endPage}, 总页数: ${totalPages}`
);
try {
console.log(`开始渲染高优先级页面: ${highPriority.join(", ")}`);
......@@ -714,31 +729,33 @@ const initBook = async () => {
const $magazine = $(magazine.value);
const expectedPages = processedPages.value.length;
const currentPages = $magazine.children().length;
console.log(`初始化turn.js: 期望页面数=${expectedPages}, 当前DOM页面数=${currentPages}`);
console.log(
`初始化turn.js: 期望页面数=${expectedPages}, 当前DOM页面数=${currentPages}`
);
// 如果DOM中的页面数量不正确,重新创建页面元素
if (currentPages !== expectedPages) {
console.log(`页面数量不匹配,重新创建页面元素`);
$magazine.empty();
// 为每个页面创建DOM元素
processedPages.value.forEach((page, index) => {
const pageDiv = document.createElement('div');
const pageDiv = document.createElement("div");
pageDiv.className = `page p${index + 1}`;
pageDiv.setAttribute('data-page-index', index);
pageDiv.setAttribute('data-page-num', page.page_num);
pageDiv.setAttribute("data-page-index", index);
pageDiv.setAttribute("data-page-num", page.page_num);
// 如果是PDF页面,添加特殊标识
if (page.isPdf) {
pageDiv.setAttribute('data-is-pdf', 'true');
pageDiv.setAttribute("data-is-pdf", "true");
}
$magazine.append(pageDiv);
});
console.log(`重新创建了 ${processedPages.value.length} 个页面元素`);
// 等待DOM更新完成
await nextTick();
}
......@@ -747,7 +764,11 @@ const initBook = async () => {
const viewportHeight = window.innerHeight;
// 使用封装的函数计算页面尺寸
const dimensions = calculatePageDimensions(viewportWidth, viewportHeight, 1.0);
const dimensions = calculatePageDimensions(
viewportWidth,
viewportHeight,
1.0
);
if (!dimensions) {
console.error("计算页面尺寸失败");
return;
......@@ -759,11 +780,19 @@ const initBook = async () => {
const pageWidth = dimensions.baseWidth;
const pageHeight = dimensions.baseHeight;
// 添加调试信息
console.log(`视口尺寸: ${viewportWidth}x${viewportHeight}`);
console.log(`计算出的页面尺寸: ${pageWidth}x${pageHeight}, 比例: ${(pageWidth/pageHeight).toFixed(3)}`);
console.log(`PDF标准比例: ${PDF_RATIO}, 实际比例: ${(pageWidth/pageHeight).toFixed(3)}`);
console.log(
`计算出的页面尺寸: ${pageWidth}x${pageHeight}, 比例: ${(
pageWidth / pageHeight
).toFixed(3)}`
);
console.log(
`PDF标准比例: ${PDF_RATIO}, 实际比例: ${(pageWidth / pageHeight).toFixed(
3
)}`
);
await nextTick();
......@@ -799,7 +828,11 @@ const initBook = async () => {
});
// 移动端使用单页显示
console.log(`turn.js初始化参数: 宽度=${isMobile.value ? pageWidth : pageWidth * 2}, 高度=${pageHeight}`);
console.log(
`turn.js初始化参数: 宽度=${
isMobile.value ? pageWidth : pageWidth * 2
}, 高度=${pageHeight}`
);
$magazine.turn({
width: isMobile.value ? pageWidth : pageWidth * 2,
height: pageHeight,
......@@ -858,10 +891,16 @@ const initBook = async () => {
initialLoading.value = false;
// 确认turn.js事件绑定状态
console.log('turn.js初始化完成,事件绑定状态:');
console.log('- turning事件:', $magazine.data('turn')?.when?.turning ? '已绑定' : '未绑定');
console.log('- turned事件:', $magazine.data('turn')?.when?.turned ? '已绑定' : '未绑定');
console.log('- 当前页码:', $magazine.turn("page"));
console.log("turn.js初始化完成,事件绑定状态:");
console.log(
"- turning事件:",
$magazine.data("turn")?.when?.turning ? "已绑定" : "未绑定"
);
console.log(
"- turned事件:",
$magazine.data("turn")?.when?.turned ? "已绑定" : "未绑定"
);
console.log("- 当前页码:", $magazine.turn("page"));
// 只处理图片页面,PDF页面在loadPdf中处理
if (!props.isPdf) {
......@@ -900,7 +939,7 @@ const loadPdf = async () => {
console.log("PDF 正在加载中,跳过重复加载");
return;
}
if (pdfDocument.value && pdfInitialized.value) {
console.log("PDF 已经加载并初始化完成,跳过重复加载");
return;
......@@ -912,15 +951,18 @@ const loadPdf = async () => {
// 加载PDF文档
if (!pdfjsLib) {
throw new Error('PDF.js not loaded');
throw new Error("PDF.js not loaded");
}
const loadingTask = pdfjsLib.getDocument(props.pdfUrl);
const rawPdfDocument = await loadingTask.promise;
pdfDocument.value = rawPdfDocument;
totalPdfPages.value = rawPdfDocument.numPages;
console.log('pdfjsViewer exists:', !!pdfjsViewer);
console.log('DefaultTextLayerFactory exists:', !!pdfjsViewer.DefaultTextLayerFactory);
console.log("pdfjsViewer exists:", !!pdfjsViewer);
console.log(
"DefaultTextLayerFactory exists:",
!!pdfjsViewer.DefaultTextLayerFactory
);
// 设置PDF链接服务
if (pdfLinkService.value) {
......@@ -939,7 +981,7 @@ const loadPdf = async () => {
// 等待Vue响应式更新完成
await nextTick();
// 初始化turn.js
await initBook();
......@@ -961,10 +1003,16 @@ const loadPdf = async () => {
// 确认PDF模式下的turn.js事件绑定状态
if (magazine.value) {
const $magazine = $(magazine.value);
console.log('PDF加载完成,turn.js事件绑定状态:');
console.log('- turning事件:', $magazine.data('turn')?.when?.turning ? '已绑定' : '未绑定');
console.log('- turned事件:', $magazine.data('turn')?.when?.turned ? '已绑定' : '未绑定');
console.log('- 当前页码:', $magazine.turn("page"));
console.log("PDF加载完成,turn.js事件绑定状态:");
console.log(
"- turning事件:",
$magazine.data("turn")?.when?.turning ? "已绑定" : "未绑定"
);
console.log(
"- turned事件:",
$magazine.data("turn")?.when?.turned ? "已绑定" : "未绑定"
);
console.log("- 当前页码:", $magazine.turn("page"));
}
console.log("PDF渲染完成");
......@@ -979,7 +1027,12 @@ watch(
() => props.pages,
(newPages, oldPages) => {
// 避免在组件初始化时重复加载
if (newPages && newPages.length > 0 && !props.isPdf && newPages !== oldPages) {
if (
newPages &&
newPages.length > 0 &&
!props.isPdf &&
newPages !== oldPages
) {
processedPages.value = processPages(newPages);
loadImages();
}
......@@ -1026,12 +1079,11 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
// 检查页面是否已经渲染过
if (renderedPages.value.has(pageIndex)) {
if (forceCheck) {
if (!pageInfo) {
console.warn(`页面${pageNum}信息获取失败,跳过scale检查`);
return;
}
const { scaleInfo } = pageInfo;
console.log(`页面 ${pageNum} 已经渲染过,但需要检查scale是否需要调整`);
// 在forceCheck模式下,检查已渲染页面的scale
......@@ -1045,7 +1097,6 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
console.log(`开始渲染PDF页面 ${pageNum},使用PdfViewer`);
try {
// 获取页面容器
......@@ -1056,21 +1107,23 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
}
// 这里是为了读取页面尺寸,这个比较准
let pageWrapper = magazine.value.querySelector(`.turn-page-wrapper[page="${pageNum}"]`);
let pageWrapper = magazine.value.querySelector(
`.turn-page-wrapper[page="${pageNum}"]`
);
if (!pageWrapper) {
console.warn(`Page wrapper not found for page ${pageNum}`);
return;
}
// 清理容器内容
pageContainer.innerHTML = '';
pageContainer.innerHTML = "";
// 创建页面div元素
// todo 这里可能能够优化一下
const pageDiv = document.createElement('div');
pageDiv.className = 'pdfViewer';
pageDiv.setAttribute('data-page-number', pageNum);
const pageDiv = document.createElement("div");
pageDiv.className = "pdfViewer";
pageDiv.setAttribute("data-page-number", pageNum);
// 将页面div添加到容器中
pageContainer.appendChild(pageDiv);
......@@ -1089,7 +1142,11 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
const pdfPage = await rawPdfDocument.getPage(pageNum);
// 使用封装的函数计算缩放比例
const scaleInfo = calculatePdfViewportScale(pdfPage, containerWidth, containerHeight);
const scaleInfo = calculatePdfViewportScale(
pdfPage,
containerWidth,
containerHeight
);
if (!scaleInfo) {
console.error(`页面${pageNum} scale 计算失败`);
return;
......@@ -1099,7 +1156,13 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
const newViewport = pdfPage.getViewport(scaleInfo.compensatedScale);
// 使用封装的函数创建 PDFPageView
const pageView = createPdfPageView(pageDiv, pageNum, scaleInfo.compensatedScale, newViewport, eventBus);
const pageView = createPdfPageView(
pageDiv,
pageNum,
scaleInfo.compensatedScale,
newViewport,
eventBus
);
// 设置PDF页面
pageView.setPdfPage(pdfPage);
......@@ -1113,24 +1176,24 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
// 标记页面已渲染
renderedPages.value.add(pageIndex);
// 验证渲染结果
const canvas = pageDiv.querySelector('canvas');
const svg = pageDiv.querySelector('svg');
const canvas = pageDiv.querySelector("canvas");
const svg = pageDiv.querySelector("svg");
const renderElement = canvas || svg;
if (renderElement) {
const rect = renderElement.getBoundingClientRect();
const elementStyle = window.getComputedStyle(renderElement);
const containerStyle = window.getComputedStyle(pageDiv);
console.log(
`PDF页面 ${pageNum} 渲染完成 (${renderElement.tagName}),尺寸: ${rect.width}x${rect.height}, 容器尺寸: ${containerWidth}x${containerHeight}`
);
if (canvas) {
console.log(`Canvas CSS尺寸: ${elementStyle.width}x${elementStyle.height}, 实际尺寸: ${canvas.width}x${canvas.height}`);
console.log(
`Canvas CSS尺寸: ${elementStyle.width}x${elementStyle.height}, 实际尺寸: ${canvas.width}x${canvas.height}`
);
}
}
......@@ -1138,7 +1201,6 @@ const renderPdfPage = async (pageIndex, forceCheck = true) => {
if (processedPages.value[pageIndex]) {
processedPages.value[pageIndex].isLoaded = true;
}
} catch (error) {
console.error(`渲染PDF页面 ${pageNum} 失败:`, error);
}
......@@ -1154,7 +1216,7 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
console.warn(`页面${pageIndex + 1}信息获取失败,跳过scale检查`);
return false;
}
const { pageContainer, pageView, scaleInfo } = pageInfo;
const newScale = scaleInfo.compensatedScale;
......@@ -1162,44 +1224,50 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
try {
// 获取当前scale值
let currentScale = newScale; // 默认值
console.log(`before pageView: ${pageView}`);
if (pageView.viewport && typeof pageView.viewport.scale === 'number') {
if (pageView.viewport && typeof pageView.viewport.scale === "number") {
currentScale = pageView.viewport.scale;
console.log(`从pageView.viewport.scale获取当前scale: ${currentScale}`);
} else {
console.warn(`页面${pageNum} viewport.scale无效,使用新计算的scale`);
}
const scaleDifference = Math.abs(currentScale - newScale);
const scaleThreshold = 0.01; // 允许的scale差异阈值
console.log(`页面${pageNum} scale检查: 当前=${currentScale}, 新计算=${newScale}, 差异=${scaleDifference}, 阈值=${scaleThreshold}`);
console.log(
`页面${pageNum} scale检查: 当前=${currentScale}, 新计算=${newScale}, 差异=${scaleDifference}, 阈值=${scaleThreshold}`
);
if (scaleDifference > scaleThreshold) {
console.log(`页面${pageNum} scale不一致,需要重新创建PDFPageView,差异: ${scaleDifference}`);
console.log(
`页面${pageNum} scale不一致,需要重新创建PDFPageView,差异: ${scaleDifference}`
);
// 销毁旧的PDFPageView实例
if (pageView && typeof pageView.destroy === 'function') {
if (pageView && typeof pageView.destroy === "function") {
pageView.destroy();
}
// 清除渲染状态
renderedPages.value.delete(pageIndex);
pdfViewerInstances.value.delete(pageIndex);
// 找到原来的pdfViewer div并清空内容
const existingPdfDiv = pageContainer.querySelector(`.pdfViewer[data-page-number="${pageNum}"]`);
const existingPdfDiv = pageContainer.querySelector(
`.pdfViewer[data-page-number="${pageNum}"]`
);
if (existingPdfDiv) {
// 直接清空现有div的内容,而不是重新创建
existingPdfDiv.innerHTML = '';
existingPdfDiv.innerHTML = "";
console.log(`找到现有pdfViewer div (页面${pageNum}),已清空内容`);
} else {
console.warn(`未找到页面${pageNum}的pdfViewer div,创建新的`);
// 如果没有找到,才创建新的
const pageDiv = document.createElement('div');
pageDiv.className = 'pdfViewer';
pageDiv.setAttribute('data-page-number', pageNum);
const pageDiv = document.createElement("div");
pageDiv.className = "pdfViewer";
pageDiv.setAttribute("data-page-number", pageNum);
pageContainer.appendChild(pageDiv);
}
......@@ -1215,7 +1283,11 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
const containerHeight = pageContainer.clientHeight;
// 使用封装的函数计算缩放比例
const scaleInfo = calculatePdfViewportScale(pdfPage, containerWidth, containerHeight);
const scaleInfo = calculatePdfViewportScale(
pdfPage,
containerWidth,
containerHeight
);
if (!scaleInfo) {
console.error(`页面${pageNum} scale检查时重新计算scale失败`);
return false;
......@@ -1225,10 +1297,17 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
const newViewport = pdfPage.getViewport(scaleInfo.compensatedScale);
// 使用现有的或新创建的div
const targetDiv = existingPdfDiv || pageContainer.querySelector('.pdfViewer');
const targetDiv =
existingPdfDiv || pageContainer.querySelector(".pdfViewer");
// 使用封装的函数创建新的 PDFPageView
const newPageView = createPdfPageView(targetDiv, pageNum, scaleInfo.compensatedScale, newViewport, eventBus);
const newPageView = createPdfPageView(
targetDiv,
pageNum,
scaleInfo.compensatedScale,
newViewport,
eventBus
);
// 设置PDF页面
newPageView.setPdfPage(pdfPage);
......@@ -1241,14 +1320,15 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
// 重新标记页面已渲染
renderedPages.value.add(pageIndex);
console.log(`页面${pageNum} PDFPageView重新创建完成,新scale: ${scaleInfo.compensatedScale}`);
console.log(
`页面${pageNum} PDFPageView重新创建完成,新scale: ${scaleInfo.compensatedScale}`
);
return true;
} else {
console.log(`页面${pageNum} scale一致,无需刷新`);
return false;
}
} catch (error) {
console.error(`检查页面${pageNum} scale失败:`, error);
return false;
......@@ -1258,12 +1338,14 @@ const checkAndUpdatePdfPageScale = async (pageIndex) => {
const getPageInfoByPageNum = (pageNum) => {
try {
// 首先尝试查找翻页容器,这个容器时动态计算的
let pageContainer = magazine.value.querySelector(`.turn-page-wrapper[page="${pageNum}"]`);
let pageContainer = magazine.value.querySelector(
`.turn-page-wrapper[page="${pageNum}"]`
);
// 检查容器尺寸
const clientWidth = pageContainer.clientWidth;
const clientHeight = pageContainer.clientHeight;
if (clientWidth === 0 || clientHeight === 0) {
console.warn(`页面${pageNum}容器尺寸为0: ${clientWidth}x${clientHeight}`);
return null;
......@@ -1285,22 +1367,26 @@ const getPageInfoByPageNum = (pageNum) => {
}
// 计算缩放比例
const scaleInfo = calculatePdfViewportScale(pdfPage, clientWidth, clientHeight);
const scaleInfo = calculatePdfViewportScale(
pdfPage,
clientWidth,
clientHeight
);
if (!scaleInfo) {
console.warn(`页面${pageNum} scale计算失败`);
return null;
}
return {
return {
pageContainer,
pageView,
scaleInfo
scaleInfo,
};
} catch (error) {
console.error(`获取页面${pageNum}信息时出错:`, error);
return null;
}
}
};
// 重新调整PDF页面尺寸
const resizePdfPage = async (pageIndex) => {
......@@ -1309,12 +1395,10 @@ const resizePdfPage = async (pageIndex) => {
const pageNum = pageIndex + 1;
try {
// 使用通用的scale检查函数
await checkAndUpdatePdfPageScale(pageIndex);
console.log(`页面${pageNum} resize 完成`);
} catch (error) {
console.error(`调整页面${pageNum}尺寸失败:`, error);
}
......@@ -1330,11 +1414,17 @@ const resizeVisiblePdfPages = async () => {
try {
const currentPage = $magazine.turn("page");
const totalPages = processedPages.value.length;
// 使用封装的函数获取可见页面索引
const visiblePages = getVisiblePageIndexes(currentPage, totalPages, isMobile.value);
const visiblePages = getVisiblePageIndexes(
currentPage,
totalPages,
isMobile.value
);
console.log(`开始检查可见页面: ${visiblePages.map(i => i + 1).join(', ')}`);
console.log(
`开始检查可见页面: ${visiblePages.map((i) => i + 1).join(", ")}`
);
// 对所有可见页面进行检查和调整
for (const pageIndex of visiblePages) {
......@@ -1349,8 +1439,7 @@ const resizeVisiblePdfPages = async () => {
}
}
console.log('所有可见 PDF 页面 resize 完成');
console.log("所有可见 PDF 页面 resize 完成");
} catch (error) {
console.error("重新调整PDF页面尺寸失败:", error);
}
......@@ -1424,7 +1513,6 @@ const zoomOut = () => {
// 提取公共的缩放更新逻辑
const updateZoomCommon = async (isResize = false) => {
if (!magazine.value || !isInitialized.value) return;
const $magazine = $(magazine.value);
......@@ -1433,8 +1521,12 @@ const updateZoomCommon = async (isResize = false) => {
try {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const dimensions = calculatePageDimensions(viewportWidth, viewportHeight, zoomLevel.value);
const dimensions = calculatePageDimensions(
viewportWidth,
viewportHeight,
zoomLevel.value
);
if (!dimensions) return;
baseWidth.value = dimensions.baseWidth;
......@@ -1442,11 +1534,9 @@ const updateZoomCommon = async (isResize = false) => {
updatePageDimensions(dimensions, $magazine);
// 等待DOM更新完成后再处理PDF页面
await nextTick();
// 使用封装的函数获取可见页面信息
// if (processedPages.value.length > 0) {
// const visiblePages = getVisiblePageInfo($magazine, processedPages);
......@@ -1455,7 +1545,7 @@ const updateZoomCommon = async (isResize = false) => {
// 处理PDF页面缩放 - 在magazine尺寸更新后重新计算scale
if (props.isPdf && pdfInitialized.value) {
await resizeVisiblePdfPages();
await resizeVisiblePdfPages();
}
// updateViewportStyles();
......@@ -1489,9 +1579,6 @@ const handleKeyDown = (e) => {
}
};
const initAudio = () => {
try {
pageTurnSound.value = new Audio(audioFlip);
......@@ -1555,8 +1642,12 @@ const toggleTextLayer = () => {
const toggleRenderer = () => {
svgRendererEnabled.value = !svgRendererEnabled.value;
console.log(`切换到${svgRendererEnabled.value ? 'SVG' : 'Canvas'}渲染模式,重新渲染所有可见页面`);
console.log(
`切换到${
svgRendererEnabled.value ? "SVG" : "Canvas"
}渲染模式,重新渲染所有可见页面`
);
// 清除渲染状态,重新渲染所有可见页面
renderedPages.value.clear();
pdfViewerInstances.value.clear(); // 清除页面视图实例
......@@ -1573,65 +1664,64 @@ const toggleRenderer = () => {
// 专门处理turn.js坐标变换的函数
const fixTurnJsCoordinateSystem = () => {
try {
const allPages = document.querySelectorAll('.pdf-page-container');
const allPages = document.querySelectorAll(".pdf-page-container");
let totalFixed = 0;
allPages.forEach(pageDiv => {
const textLayer = pageDiv.querySelector('.textLayer');
allPages.forEach((pageDiv) => {
const textLayer = pageDiv.querySelector(".textLayer");
if (!textLayer) return;
const textDivs = textLayer.querySelectorAll('div');
const container = pageDiv.closest('.pdf-page-container');
const turnPage = container ? container.closest('.page') : null;
const textDivs = textLayer.querySelectorAll("div");
const container = pageDiv.closest(".pdf-page-container");
const turnPage = container ? container.closest(".page") : null;
if (!turnPage || !container) return;
const pageRect = turnPage.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// 计算turn.js的缩放比例
const scaleX = pageRect.width / containerRect.width;
const scaleY = pageRect.height / containerRect.height;
textDivs.forEach(div => {
textDivs.forEach((div) => {
// 获取原始位置数据
const calculatedPos = div.getAttribute('data-calculated-pos');
const calculatedPos = div.getAttribute("data-calculated-pos");
if (calculatedPos) {
try {
const pos = JSON.parse(calculatedPos);
// 应用turn.js的缩放修正
const correctedX = pos.x * scaleX;
const correctedY = pos.y * scaleY;
// 强制应用修正后的位置
div.style.left = `${correctedX}px !important`;
div.style.top = `${correctedY}px !important`;
// 更新CSS变量
div.style.setProperty('--text-left', `${correctedX}px`);
div.style.setProperty('--text-top', `${correctedY}px`);
div.style.setProperty("--text-left", `${correctedX}px`);
div.style.setProperty("--text-top", `${correctedY}px`);
totalFixed++;
} catch (e) {
console.error('解析位置数据失败:', e);
console.error("解析位置数据失败:", e);
}
}
});
});
if (totalFixed > 0) {
console.log(`修正了 ${totalFixed} 个文本元素的turn.js坐标变换`);
}
return totalFixed;
} catch (error) {
console.error('修正turn.js坐标变换失败:', error);
console.error("修正turn.js坐标变换失败:", error);
return 0;
}
};
// 添加调试函数:检查页面状态
const debugPageStatus = () => {
if (!magazine.value) return;
......@@ -1654,13 +1744,20 @@ const debugPageStatus = () => {
if (pageView) {
// 使用更安全的方式检查状态,避免访问私有字段
try {
const hasTextLayer = pageView.textLayer && typeof pageView.textLayer === 'object';
const hasAnnotationLayer = pageView.annotationLayer && typeof pageView.annotationLayer === 'object';
const hasTextLayer =
pageView.textLayer && typeof pageView.textLayer === "object";
const hasAnnotationLayer =
pageView.annotationLayer &&
typeof pageView.annotationLayer === "object";
console.log(
`当前页面PdfViewer状态: 实例存在=true, 文字层=${hasTextLayer ? '启用' : '禁用'}, 注释层=${hasAnnotationLayer ? '启用' : '禁用'}`
`当前页面PdfViewer状态: 实例存在=true, 文字层=${
hasTextLayer ? "启用" : "禁用"
}, 注释层=${hasAnnotationLayer ? "启用" : "禁用"}`
);
} catch (error) {
console.log(`当前页面PdfViewer状态: 实例存在=true, 状态检查失败=${error.message}`);
console.log(
`当前页面PdfViewer状态: 实例存在=true, 状态检查失败=${error.message}`
);
}
} else {
console.log(`当前页面没有PdfViewer实例`);
......@@ -1685,7 +1782,7 @@ let resizeTimeout;
onMounted(async () => {
// 先加载PDF.js
await loadPdfJs();
// 配置PDF.js worker - 使用标准的worker文件而不是.mjs
if (pdfjsLib && pdfjsLib.GlobalWorkerOptions) {
// 使用pdfjs-dist/build目录下的标准worker文件
......@@ -1714,33 +1811,33 @@ onMounted(async () => {
// 根据类型选择加载方式
if (props.isPdf && props.pdfUrl) {
console.log('组件挂载时加载PDF:', props.pdfUrl);
console.log("组件挂载时加载PDF:", props.pdfUrl);
await loadPdf();
} else if (props.pages && props.pages.length > 0) {
console.log('组件挂载时加载图片');
console.log("组件挂载时加载图片");
await loadImages();
} else {
console.log('组件挂载时无需加载内容');
console.log("组件挂载时无需加载内容");
}
initAudio();
window.addEventListener("keydown", handleKeyDown);
// 添加防抖的resize监听器
console.log('绑定resize事件监听器');
console.log("绑定resize事件监听器");
window.addEventListener("resize", () => {
console.log('resize事件触发');
console.log("resize事件触发");
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
console.log('调用handleResize');
console.log("调用handleResize");
handleResize();
}, 250);
});
// Load directory images
loadDirectoryImages();
console.log('onMounted完成,事件绑定完成');
console.log("onMounted完成,事件绑定完成");
});
onUnmounted(() => {
......@@ -1757,11 +1854,11 @@ onUnmounted(() => {
// 清理PdfViewer实例
pdfViewerInstances.value.forEach((pageView) => {
if (pageView && typeof pageView.destroy === 'function') {
if (pageView && typeof pageView.destroy === "function") {
try {
pageView.destroy();
} catch (error) {
console.warn('清理PdfViewer实例时出错:', error);
console.warn("清理PdfViewer实例时出错:", error);
}
}
});
......@@ -1845,10 +1942,10 @@ const goToPage = async (pageNum) => {
if (magazine.value && isInitialized.value) {
const $magazine = $(magazine.value);
$magazine.turn("page", pageNum);
// 等待turn.js完成页面跳转
await new Promise((resolve) => setTimeout(resolve, 100));
// 如果是PDF模式,重新调整页面尺寸
if (props.isPdf && pdfInitialized.value) {
await resizeVisiblePdfPages();
......@@ -1863,22 +1960,13 @@ const showGuide = () => {
guidePcRef.value.show();
}
};
defineExpose({
goToPage,
});
</script>
<style scoped lang="scss">
// .page {
// width: 100%;
// height: 100%;
// background-color: white !important; // 确保翻页时有白色背景
// overflow: hidden;
// backface-visibility: visible; // 确保背面可见
// img {
// width: 100%;
// height: 100%;
// object-fit: contain;
// }
// }
.book-reader-container {
position: relative;
width: 100%;
......
<template>
<el-drawer
v-loading="loading"
v-model="showDrawer"
title="全文搜索"
direction="ttb"
:before-close="handleClose"
:size="searchResult.length > 0 ? '500px' : '200px'"
>
<div class="wrapper">
<el-input
v-model="keyword"
@keyup.enter="onSearch"
placeholder="请输入关键词进行全文搜索"
clearable
>
<template #append>
<el-button :icon="Search" @click="onSearch" />
</template>
</el-input>
<div class="list">
<div
class="list-item"
v-for="value in searchResult"
:key="value.book_id"
@click="handleLocatePage(value.page_num)"
>
<div v-html="value.text" class="line-clamp-3"></div>
<el-tag type="primary" class="pageNum"
>第{{ value.page_num }}页</el-tag
>
</div>
</div>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
import { searchText } from "@/api";
import { Search } from "@element-plus/icons-vue";
/* props */
/* emit */
const $emit = defineEmits(["locatePage"]);
/* data */
const showDrawer = ref(false);
const keyword = ref("");
const loading = ref(false);
const searchResult = ref([]);
/* computed */
/* methods */
function show() {
showDrawer.value = true;
}
function handleClose() {
showDrawer.value = false;
}
async function onSearch() {
console.log(keyword.value);
try {
loading.value = true;
const params = { q: keyword.value };
const res = await searchText(params);
const { results } = res;
searchResult.value = results.map((item) => {
return {
...item,
url: import.meta.env.VITE_API_BASE_URL + "/static/" + item.url,
};
});
} catch (error) {
console.log(error);
}
}
function handleLocatePage(param) {
$emit("locatePage", param);
showDirectory.value = false;
}
defineExpose({
show,
});
</script>
<style lang="scss" scoped>
.list {
padding: 20px 0;
height: 300px;
overflow: auto;
.list-item {
padding: 20px;
font-size: 14px;
color: #666;
border-bottom: 1px solid #eee;
display: flex;
cursor: pointer;
&:hover {
background-color: #eee;
}
}
}
:deep(.el-drawer__body) {
height: 500px;
}
.wrapper {
padding: 0 50px;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
}
</style>
......@@ -4,7 +4,15 @@
<el-button type="primary" link class="back" @click="goBack"
>返回</el-button
>
微方志
{{ title || "微方志" }}
<el-button
type="text"
class="search"
:icon="Search"
@click="handleOpenSearch"
>
全文搜索
</el-button>
</div>
<BookReader
:pages="pages"
......@@ -12,12 +20,15 @@
:pdfUrl="pdfUrl"
:bookmarks="bookmarks"
:enableTextLayer="true"
ref="bookReaderRef"
/>
<SearchBar ref="searchBarRef" @locatePage="locatePage" />
</div>
</template>
<script setup>
import { getDocumentDetail, getDocumentBookmarks } from "@/api";
import BookReader from "@/components/BookReader/index.vue";
import { Search } from "@element-plus/icons-vue";
const route = useRoute();
const router = useRouter();
/* props */
......@@ -27,6 +38,9 @@ const title = ref("");
const pages = ref([]);
const bookmarks = ref([]);
const pdfUrl = ref("");
const searchBarRef = ref(null);
const bookReaderRef = ref(null);
/* computed */
/* methods */
async function loadData(id) {
......@@ -63,6 +77,16 @@ async function fetchBookmarks(id) {
}
}
function handleOpenSearch() {
searchBarRef.value.show();
}
function locatePage(pageNum) {
if (bookReaderRef.value) {
bookReaderRef.value.goToPage(pageNum);
}
}
onMounted(() => {
const { id } = route.params;
title.value = route.query.title;
......@@ -85,12 +109,19 @@ onMounted(() => {
align-items: center;
position: relative;
height: 46px;
padding: 0 20px;
padding: 0 40px;
.back {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
}
.search {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
color: #fff;
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论