<template>
<view class="webview-container">
<cover-view class="webview-wrapper">
<web-view :src="webviewPath" id="targetWebview" allow-same-origin sandbox="allow-same-origin allow-scripts" ref="webviewRef"></web-view>
</cover-view>
<cover-view class="bottom-action-area">
<cover-view class="capture-btn" @click="captureWebview">
<cover-view class="btn-text">立即取证</cover-view>
</cover-view>
</cover-view>
<!-- 预览组件 -->
<cover-view v-if="showPreview" class="custom-preview">
<cover-view class="preview-header">
<cover-view class="title-container">
<cover-view class="preview-title">预览界面</cover-view>
</cover-view>
</cover-view>
<cover-image
:src="screenshotPath"
class="preview-image"
mode="aspectFit"
@error="handleImageError"
></cover-image >
<cover-view class="action-buttons">
<cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view>
<cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view>
</cover-view>
<cover-view>
</cover-view>
</cover-view>
</view>
</template>
<script setup lang="ts" >
import { onLoad, onReady } from '@dcloudio/uni-app';
import html2canvas from 'html2canvas';
import { nextTick, ref } from 'vue';
declare const plus: any;
const webviewPath = ref('');
const ws = ref<any>(null);
const screenshotPath = ref('');
const showPreview = ref(false);
const platform = ref('');
const originalWebviewHeight = ref('auto');
const screenshotFilePath = ref('');
const screenshot = ref('');
const iframeLoaded = ref(false);
onLoad((options: any) => {
if (options.url) {
webviewPath.value = decodeURIComponent(options.url);
}
// 获取当前平台
const systemInfo = uni.getSystemInfoSync();
platform.value = systemInfo.platform;
});
onReady(() => {
// #ifdef APP-PLUS
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentWebview = currentPage.$getAppWebview();
setTimeout(() => {
if (currentWebview.children().length > 0) {
const wv = currentWebview.children()[0];
ws.value = wv;
// originalWebviewHeight.value = wv.getStyle().height || 'auto';
wv.setStyle({ height: 'auto' });
}
}, 1000);
// #endif
// #ifdef H5
const iframe = document.getElementById('targetWebview');
console.log(iframe)
if (!iframe) {
console.warn('未找到 iframe 元素');
return;
}
// 设置加载监听
// #endif
});
const captureWebview = async () => {
uni.showLoading({
title: '正在取证中'
})
// #ifdef APP-PLUS
if (!ws.value) {
uni.showToast({ title: '网页未加载完成', icon: 'none' });
return;
}
const bitmap = new plus.nativeObj.Bitmap('screenshot')
const rand = Math.floor(Math.random() * 10000)
const saveUrl = '_doc/' + rand + 'order.jpg'
// 绘制页面截图
ws.value.draw(bitmap, () => {
// 保存图片
bitmap.save(
saveUrl,
{ format: 'jpg', quality: 90 },
(res:any) => {
// 将本地路径转换为可以展示的路径
const previewPath = plus.io.convertLocalFileSystemURL(res.target)
screenshotPath.value = previewPath
screenshotFilePath.value = res.target; // 保存文件路径
console.log('截图保存成功,预览路径:', previewPath)
// 延迟展示,确保加载完成
setTimeout(() => {
showPreview.value = true
uni.hideLoading()
}, 300)
bitmap.clear() // 清理 bitmap 占用资源
},
(err:any) => {
console.error('保存失败:', err)
uni.hideLoading()
uni.showToast({
title: '保存失败: ' + err.message,
icon: 'none',
})
bitmap.clear()
}
)
}, (err:any) => {
console.error('截图失败:', err)
uni.showToast({
title: '截图失败: ' + err.message,
icon: 'none',
})
uni.hideLoading()
})
// #endif
// #ifdef H5
const iframe = document.getElementById('targetWebview')
if (!iframe) {
throw new Error("未找到 iframe 元素");
}
html2canvas(iframe, {
width: iframe.clientWidth, //dom 原始宽度
height: iframe.clientHeight,
scrollY: 0, //html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
scrollX: 0,
useCORS: true //支持跨域
}).then((canvas)=>{
let base64 = canvas.toDataURL('image/png');
screenshotPath.value = base64;
// 这里就按照chrome等新版浏览器来处理
// const a = document.createElement('a');
// a.href = base64;
// a.setAttribute('download', 'pigeons-download');
// a.click();
setTimeout(() => {
showPreview.value = true
uni.hideLoading()
}, 300)
})
// #endif
}
const closePreview = () => {
showPreview.value = false;
}
//上传
const uploadEvidence = () => {
// #ifdef APP-PLUS
// APP环境上传
plus.io.resolveLocalFileSystemURL(screenshotFilePath.value,
(entry:any) => {
console.log('文件存在,开始上传:', entry.name);
// 继续执行上传逻辑
uni.showLoading({ title: '上传证据中...', mask: true });
// 获取文件系统路径
const filePath = entry.toLocalURL();
uni.uploadFile({
url: 'http://192.168.1.80:1592/api/upload',
filePath: filePath,
name: 'file',
type: 'image',
formData: {
'timestamp': Date.now()
},
success: (uploadRes) => {
uni.hideLoading();
try {
const data = JSON.parse(uploadRes.data);
if (data.success) {
uni.showToast({ title: '证据上传成功', icon: 'none' })
} else {
uni.showToast({ title: '上传失败', icon: 'none' })
console.log('上传失败')
}
} catch (e) {
uni.showToast({ title: '解析响应失败', icon: 'none' })
}
// 3秒后关闭状态提示
setTimeout(() => {
closePreview();
}, 3000);
},
fail: (err) => {
uni.hideLoading();
console.error('上传错误详情:', err);
uni.showToast({ title: '网络错误'+ err.errMsg, icon: 'none' })
setTimeout(() => {
}, 3000);
}
});
},
(err:any) => {
console.error('文件不存在或路径错误:', err);
uni.showToast({ title: '文件路径无效', icon: 'none' });
}
);
uni.showLoading({ title: '上传证据中...', mask: true });
// #endif
// #ifdef APP-H5
// H5环境上传
// if (!screenshotFilePath.value) {
// uni.hideLoading();
// uni.showToast({ title: '文件路径无效', icon: 'none' });
// return;
// }
// 获取文件对象
// fetch(screenshotFilePath.value)
// .then(res => res.blob())
// .then(blob => {
// const formData = new FormData();
// formData.append('evidence', blob, `evidence-${Date.now()}.jpg`);
// formData.append('timestamp', Date.now().toString());
// return fetch('http://192.168.1.80:1592/api/upload', {
// method: 'POST',
// body: formData
// });
// })
// .then(response => response.json())
// .then(data => {
// uni.hideLoading();
// if (data.success) {
// uni.showToast({ title: '证据上传成功', icon: 'none' });
// } else {
// uni.showToast({ title: '上传失败'+ data.message, icon: 'none' });
// }
// setTimeout(() => {
// closePreview();
// }, 3000);
// })
// .catch(err => {
// uni.hideLoading();
// uni.showToast({ title: '上传失败'+ err.message, icon: 'none' })
// setTimeout(() => {
// }, 3000);
// });
// #endif
}
const handleAction = (action: string) => {
switch(action) {
case '放弃':
closePreview();
break;
case '固化':
// #ifdef APP-PLUS
uni.saveImageToPhotosAlbum({
filePath: screenshotPath.value,
success: () => {
uni.showToast({ title: '证据已固化保存' });
// 调用上传函数
uploadEvidence();
},
fail: (err) => {
uni.showToast({ title: '保存失败: ' + err.errMsg, icon: 'none' });
}
});
// #endif
// #ifdef APP-H5
// #endif
break;
}
}
const handleImageError = (e: any) => {
console.error('图片加载错误:', e);
uni.showToast({ title: '图片加载错误', icon: 'none' });
}
</script>
<style>
.webview-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.webview-wrapper {
height: calc(100vh - 120rpx);
width: 100%;
overflow: hidden;
}
.bottom-action-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background-color: #007aff;
padding: 30rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
display: flex;
justify-content: center;
align-items: center;
}
.capture-btn {
width: 100%;
height: 90rpx;
background-color: #007aff;
border-radius: 45rpx;
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.btn-text {
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
}
/* 优化预览组件 */
.custom-preview {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column; /* 垂直排列子元素 */
align-items: center;
/* 关键修改:去掉顶部内边距,只保留底部内边距 */
padding: 0 0 40rpx 0;
box-sizing: border-box;
}
.preview-header {
width: 100%;
height: 250rpx; /* 增加高度 */
background-color: #007aff;
color: #ffffff;
font-size: 34rpx; /* 增大字体 */
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
/* 添加圆角效果 */
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
.preview-title {
color: #ffffff; /* 白色字体 */
font-size: 28rpx; /* 字体大小适配40rpx高度 */
font-weight: bold;
line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */
width: 100%;
text-align: center;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: contain;
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 8rpx;
background-color: #f5f5f5;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
}
.action-buttons {
display: flex;
width: 100%; /* 充满整个宽度 */
justify-content: space-between;
padding: 30rpx 5%; /* 左右留5%间隙,上下30rpx内边距扩展背景区域 */
margin-top: auto; /* 借助flex布局推到最底部 */
background-color: #353336; /* 底部模块背景色 */
box-sizing: border-box; /* 确保padding不影响宽度计算 */
}
.action-btn {
flex: 1;
height: 90rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0 15rpx;
border: none;
color: #ffffff; /* 文字色适配深色背景 */
font-size: 34rpx;
font-weight: 500;
line-height: 1;
}
.cancel-btn {
color: #ffffff; /* 文字颜色 */
font-size: 34rpx;
font-weight: 500;
/* 确保文字本身无偏移 */
line-height: 1; /* 清除行高影响 */
text-align: center; /* 辅助居中(冗余保障) */
background-color: #353336;
}
.confirm-btn {
color: #ffffff; /* 文字颜色 */
font-size: 34rpx;
font-weight: 500;
/* 确保文字本身无偏移 */
line-height: 1; /* 清除行高影响 */
text-align: center; /* 辅助居中(冗余保障) */
background-color: #353336;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.bottom-action-img{
position: fixed;
top: 100;
left: 0;
right: 0;
z-index: 100;
background-color: #007aff;
padding: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
.action-img{
width: 100%;
height: 90rpx;
background-color: #007aff;
display: flex;
}
</style>
截屏的内容为空白 根据这个代码修改
最新发布