Otsuka-APP/utils/utils.js

703 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 格式化价格,保留两位小数
export const formatPrice = (value) => {
if (!value && value !== 0) return '0.00'
return Number(value).toFixed(2)
}
import { securityFileDownload } from '@/api/FileUpload/FileUpload.js';
import * as pdfjsLib from 'pdfjs-dist';
// 设置PDF.js worker路径使用CDN
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
// 存储当前页面的弹框实例
let popupInstance = null;
let styleInjected = false;
// 注入CSS样式
const injectPdfStyle = () => {
if (styleInjected) return;
const styleId = 'pdf_preview_style';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.innerHTML = `
.pdf-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.pdf-preview-container {
width: 95vw;
max-width: 900px;
height: 90vh;
background-color: #fff;
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
animation: pdfFadeIn 0.3s;
}
@keyframes pdfFadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.pdf-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #eee;
background-color: #fff;
}
.pdf-title {
font-size: 16px;
font-weight: bold;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdf-close {
font-size: 28px;
color: #999;
cursor: pointer;
padding: 0 8px;
line-height: 1;
}
.pdf-close:hover {
color: #333;
}
.pdf-content {
flex: 1;
background-color: #525659;
position: relative;
overflow: auto;
padding: 20px;
box-sizing: border-box;
}
.pdf-canvas-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.pdf-page-canvas {
width: 100%;
height: auto;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-radius: 4px;
background-color: white;
}
.pdf-footer {
display: flex;
justify-content: flex-end;
padding: 12px 16px;
border-top: 1px solid #eee;
background-color: #fff;
}
.pdf-footer button {
margin: 0 0 0 8px;
min-width: 80px;
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
cursor: pointer;
font-size: 14px;
}
.pdf-footer button:hover {
background-color: #f5f7fa;
}
.pdf-footer button.primary {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
.pdf-footer button.primary:hover {
background-color: #66b1ff;
}
.pdf-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 16px;
text-align: center;
}
.pdf-pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
background-color: #fff;
border-top: 1px solid #eee;
}
.pdf-pagination button {
margin: 0 8px;
padding: 4px 12px;
border: 1px solid #dcdfe6;
background-color: #fff;
border-radius: 4px;
cursor: pointer;
}
.pdf-pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pdf-pagination span {
margin: 0 8px;
color: #606266;
}
`;
document.head.appendChild(style);
styleInjected = true;
}
};
// H5环境使用Canvas渲染PDF
const createH5Popup = (url, fileName) => {
// 先关闭已存在的弹框
if (popupInstance) {
document.body.removeChild(popupInstance);
popupInstance = null;
}
// 注入样式
injectPdfStyle();
// 创建遮罩层
const overlay = document.createElement('div');
overlay.className = 'pdf-preview-overlay';
// 创建弹框容器
const container = document.createElement('div');
container.className = 'pdf-preview-container';
// 创建头部
const header = document.createElement('div');
header.className = 'pdf-header';
const title = document.createElement('span');
title.className = 'pdf-title';
title.textContent = fileName || 'PDF预览';
const closeBtn = document.createElement('span');
closeBtn.className = 'pdf-close';
closeBtn.textContent = '×';
closeBtn.onclick = () => {
document.body.removeChild(overlay);
if (url && url.startsWith('blob:')) {
URL.revokeObjectURL(url);
}
popupInstance = null;
};
header.appendChild(title);
// header.appendChild(closeBtn);
// 创建内容区域
const content = document.createElement('div');
content.className = 'pdf-content';
// 创建Canvas容器
const canvasContainer = document.createElement('div');
canvasContainer.className = 'pdf-canvas-container';
content.appendChild(canvasContainer);
// 添加加载提示
const loadingDiv = document.createElement('div');
loadingDiv.className = 'pdf-loading';
loadingDiv.textContent = '正在加载PDF文档...';
content.appendChild(loadingDiv);
container.appendChild(header);
container.appendChild(content);
// 创建底部
const footer = document.createElement('div');
footer.className = 'pdf-footer';
const closeFooterBtn = document.createElement('button');
closeFooterBtn.textContent = '关闭';
closeFooterBtn.onclick = closeBtn.onclick;
const downloadBtn = document.createElement('button');
downloadBtn.textContent = '下载';
downloadBtn.className = 'primary';
downloadBtn.onclick = () => {
window.open(url);
};
// footer.appendChild(downloadBtn);
footer.appendChild(closeFooterBtn);
container.appendChild(footer);
overlay.appendChild(container);
document.body.appendChild(overlay);
// 使用PDF.js渲染PDF
pdfjsLib.getDocument(url).promise.then(pdf => {
// 移除加载提示
if (content.contains(loadingDiv)) {
content.removeChild(loadingDiv);
}
// 添加分页控制器
const paginationDiv = document.createElement('div');
paginationDiv.className = 'pdf-pagination';
let currentPage = 1;
const totalPages = pdf.numPages;
const prevBtn = document.createElement('button');
prevBtn.textContent = '上一页';
prevBtn.disabled = true;
const nextBtn = document.createElement('button');
nextBtn.textContent = '下一页';
const pageInfo = document.createElement('span');
pageInfo.textContent = `${currentPage} / ${totalPages}`;
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderPage(currentPage);
pageInfo.textContent = `${currentPage} / ${totalPages}`;
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages;
}
};
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderPage(currentPage);
pageInfo.textContent = `${currentPage} / ${totalPages}`;
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages;
}
};
// paginationDiv.appendChild(prevBtn);
// paginationDiv.appendChild(pageInfo);
// paginationDiv.appendChild(nextBtn);
// content.appendChild(paginationDiv);
// 创建Canvas元素
const canvas = document.createElement('canvas');
canvas.className = 'pdf-page-canvas';
canvasContainer.appendChild(canvas);
// 渲染指定页面
const renderPage = (pageNumber) => {
pdf.getPage(pageNumber).then(page => {
// 根据容器宽度计算缩放比例
const containerWidth = content.clientWidth - 40; // 减去padding
const viewport = page.getViewport({ scale: 1.0 });
const scale = containerWidth / viewport.width;
const scaledViewport = page.getViewport({ scale });
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
const context = canvas.getContext('2d');
const renderContext = {
canvasContext: context,
viewport: scaledViewport
};
return page.render(renderContext).promise;
});
};
// 渲染第一页
renderPage(1);
// 监听窗口大小变化,重新渲染
const resizeHandler = () => {
renderPage(currentPage);
};
window.addEventListener('resize', resizeHandler);
// 清理事件监听
const originalClose = closeBtn.onclick;
closeBtn.onclick = () => {
window.removeEventListener('resize', resizeHandler);
originalClose.call(closeBtn);
};
}).catch(error => {
console.error('PDF渲染失败:', error);
if (content.contains(loadingDiv)) {
content.removeChild(loadingDiv);
}
content.innerHTML = `
<div style="display:flex; flex-direction:column; justify-content:center; align-items:center; height:100%; padding:20px; text-align:center;">
<div style="color:#f56c6c; font-size:18px; margin-bottom:16px;">
<span style="display:block; margin-bottom:8px;">⚠️ PDF预览失败</span>
</div>
<div style="color:#999; font-size:14px; margin-bottom:24px; max-width:80%; word-break:break-all;">
${error.message || '未知错误'}
</div>
<button onclick="window.open('${url}')"
style="padding:12px 32px; background-color:#409eff; color:#fff; border:none; border-radius:4px; cursor:pointer; font-size:16px; font-weight:500;">
下载PDF文件
</button>
</div>
`;
});
popupInstance = overlay;
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeBtn.onclick();
}
});
return overlay;
};
// APP环境保存Blob到临时文件
const saveBlobToFile = (blob, fileName) => {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
const reader = new FileReader();
reader.onload = (e) => {
const base64Data = e.target.result.split(',')[1];
const filePath = '_doc/' + Date.now() + '_' + fileName;
plus.io.writeFile({
filename: filePath,
data: base64Data,
success: () => {
resolve(filePath);
},
fail: (err) => {
reject(err);
}
});
};
reader.onerror = (err) => {
reject(err);
};
reader.readAsDataURL(blob);
// #endif
// #ifndef APP-PLUS
reject(new Error('非APP环境'));
// #endif
});
};
// APP环境使用本地文件预览
const createAppPopup = (filePath, fileName) => {
// #ifdef APP-PLUS
if (popupInstance) {
popupInstance.close();
popupInstance = null;
}
const popupId = 'pdf_preview_' + Date.now();
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #fff;
height: 100vh;
display: flex;
flex-direction: column;
}
.pdf-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #eee;
background-color: #fff;
}
.pdf-title {
font-size: 16px;
font-weight: bold;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdf-close {
font-size: 28px;
color: #999;
cursor: pointer;
padding: 0 8px;
}
.pdf-content {
flex: 1;
background-color: #525659;
overflow: auto;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.pdf-canvas {
width: 100%;
height: auto;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-radius: 4px;
margin-bottom: 20px;
}
.pdf-footer {
display: flex;
justify-content: flex-end;
padding: 12px 16px;
border-top: 1px solid #eee;
background-color: #fff;
}
.pdf-footer button {
margin: 0 0 0 8px;
min-width: 80px;
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
font-size: 14px;
cursor: pointer;
}
.pdf-footer button.primary {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
.pdf-loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 16px;
}
</style>
</head>
<body>
<div class="pdf-header">
<span class="pdf-title">${fileName || 'PDF预览'}</span>
<span class="pdf-close" onclick="closePopup()">×</span>
</div>
<div class="pdf-content" id="pdfContent">
<div class="pdf-loading" id="loading">正在加载PDF...</div>
</div>
<div class="pdf-footer">
<button onclick="downloadFile()" class="primary">下载</button>
<button onclick="closePopup()">关闭</button>
</div>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
const url = '${filePath}';
const content = document.getElementById('pdfContent');
const loading = document.getElementById('loading');
pdfjsLib.getDocument(url).promise.then(pdf => {
loading.style.display = 'none';
const renderPage = (pageNumber) => {
pdf.getPage(pageNumber).then(page => {
const containerWidth = content.clientWidth - 40;
const viewport = page.getViewport({ scale: 1.0 });
const scale = containerWidth / viewport.width;
const scaledViewport = page.getViewport({ scale });
const canvas = document.createElement('canvas');
canvas.className = 'pdf-canvas';
canvas.height = scaledViewport.height;
canvas.width = scaledViewport.width;
const context = canvas.getContext('2d');
const renderContext = {
canvasContext: context,
viewport: scaledViewport
};
page.render(renderContext).promise.then(() => {
content.appendChild(canvas);
// 渲染下一页
if (pageNumber < pdf.numPages) {
renderPage(pageNumber + 1);
}
});
});
};
renderPage(1);
}).catch(error => {
loading.style.display = 'none';
content.innerHTML = '<div style="color:#f56c6c; text-align:center; padding:40px;">PDF加载失败<br><br><button onclick="downloadFile()" style="padding:8px 24px; background:#409eff; color:#fff; border:none; border-radius:4px;">下载文件</button></div>';
});
function closePopup() {
plus.webview.currentWebview().close();
}
function downloadFile() {
plus.runtime.openWeb('${filePath}');
}
<\/script>
</body>
</html>`;
const webview = plus.webview.create('', popupId, {
top: '10vh',
left: '5vw',
width: '90vw',
height: '80vh',
scrollIndicator: 'none',
backgroundColor: '#ffffff',
borderRadius: '12px',
mask: 'rgba(0,0,0,0.5)'
});
webview.loadHTML(html);
webview.show();
popupInstance = webview;
return webview;
// #endif
};
// 主函数
export const YuLanfile = async (filePath, returnUrlOnly = false) => {
if (!filePath) {
uni.showToast({ title: '文件路径不存在', icon: 'none' })
return
}
try {
uni.showLoading({ title: '加载中...', mask: true })
const response = await securityFileDownload({
fileName: filePath,
delete: false
})
// 转换为 Blob
let blob;
if (response instanceof ArrayBuffer) {
blob = new Blob([response], { type: 'application/pdf' });
} else if (response instanceof Blob) {
blob = response;
} else {
try {
const uint8Array = new Uint8Array(response.length);
for (let i = 0; i < response.length; i++) {
uint8Array[i] = response.charCodeAt(i) & 0xFF;
}
blob = new Blob([uint8Array], { type: 'application/pdf' });
} catch (e) {
throw new Error('无法转换文件格式');
}
}
// 获取文件名
const fileName = filePath.split('\\').pop() || 'document.pdf';
// #ifdef H5
// H5环境创建本地文件URL
const url = URL.createObjectURL(blob);
if (returnUrlOnly) {
uni.hideLoading();
return url;
}
// 使用Canvas渲染PDF
createH5Popup(url, fileName);
uni.hideLoading();
return url;
// #endif
// #ifdef APP-PLUS
// APP环境保存到临时文件
const tempFilePath = await saveBlobToFile(blob, fileName);
if (returnUrlOnly) {
uni.hideLoading();
return tempFilePath;
}
createAppPopup(tempFilePath, fileName);
uni.hideLoading();
return tempFilePath;
// #endif
// #ifdef MP-WEIXIN
// 微信小程序使用wx.openDocument
const wxUrl = URL.createObjectURL(blob);
const fs = wx.getFileSystemManager();
const tempFilePath = wx.env.USER_DATA_PATH + '/' + Date.now() + '_' + fileName;
// 将blob转换为ArrayBuffer
const arrayBuffer = await blob.arrayBuffer();
fs.writeFile({
filePath: tempFilePath,
data: arrayBuffer,
encoding: 'binary',
success: () => {
wx.openDocument({
filePath: tempFilePath,
showMenu: true,
success: () => {
URL.revokeObjectURL(wxUrl);
},
fail: (err) => {
console.error('打开文档失败:', err);
uni.showToast({ title: '打开文件失败', icon: 'none' });
URL.revokeObjectURL(wxUrl);
}
});
},
fail: (err) => {
console.error('写入文件失败:', err);
uni.showToast({ title: '文件处理失败', icon: 'none' });
URL.revokeObjectURL(wxUrl);
}
});
uni.hideLoading();
return tempFilePath;
// #endif
} catch (error) {
console.error('文件预览失败:', error);
uni.hideLoading();
uni.showToast({ title: '文件加载失败', icon: 'none' });
throw error;
}
}