html2canvas
This commit is contained in:
parent
57eb948d37
commit
2cc477dac9
50
package-lock.json
generated
50
package-lock.json
generated
@ -18,7 +18,9 @@
|
|||||||
"@capgo/camera-preview": "^8.3.1",
|
"@capgo/camera-preview": "^8.3.1",
|
||||||
"@ionic/vue": "^8.0.0",
|
"@ionic/vue": "^8.0.0",
|
||||||
"@ionic/vue-router": "^8.0.0",
|
"@ionic/vue-router": "^8.0.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
|
"silly-datetime": "^0.1.2",
|
||||||
"vant": "^4.9.24",
|
"vant": "^4.9.24",
|
||||||
"vconsole": "^3.15.1",
|
"vconsole": "^3.15.1",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
@ -35,7 +37,6 @@
|
|||||||
"eslint-plugin-vue": "^9.9.0",
|
"eslint-plugin-vue": "^9.9.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"postcss-px-to-viewport": "^1.1.1",
|
"postcss-px-to-viewport": "^1.1.1",
|
||||||
"silly-datetime": "^0.1.2",
|
|
||||||
"terser": "^5.4.0",
|
"terser": "^5.4.0",
|
||||||
"typescript": "~5.9.0",
|
"typescript": "~5.9.0",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
@ -4023,6 +4024,14 @@
|
|||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-arraybuffer": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
@ -4581,6 +4590,14 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-line-break": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@ -5866,6 +5883,18 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html2canvas": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||||
|
"dependencies": {
|
||||||
|
"css-line-break": "^2.1.0",
|
||||||
|
"text-segmentation": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||||
@ -7972,8 +8001,7 @@
|
|||||||
"node_modules/silly-datetime": {
|
"node_modules/silly-datetime": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/silly-datetime/-/silly-datetime-0.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/silly-datetime/-/silly-datetime-0.1.2.tgz",
|
||||||
"integrity": "sha512-q8hnO91rRvQsYTYaZCJc6UpljzfdmWD3bNljDLKGVBT2ukj7snE+ENkVVkXfo529ABLEBeN6PHoEaT1ONEq81w==",
|
"integrity": "sha512-q8hnO91rRvQsYTYaZCJc6UpljzfdmWD3bNljDLKGVBT2ukj7snE+ENkVVkXfo529ABLEBeN6PHoEaT1ONEq81w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@ -8261,6 +8289,14 @@
|
|||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/text-segmentation": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
||||||
@ -8593,6 +8629,14 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/utrie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-arraybuffer": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
|
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
|||||||
@ -23,12 +23,13 @@
|
|||||||
"@capgo/camera-preview": "^8.3.1",
|
"@capgo/camera-preview": "^8.3.1",
|
||||||
"@ionic/vue": "^8.0.0",
|
"@ionic/vue": "^8.0.0",
|
||||||
"@ionic/vue-router": "^8.0.0",
|
"@ionic/vue-router": "^8.0.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
|
"silly-datetime": "^0.1.2",
|
||||||
"vant": "^4.9.24",
|
"vant": "^4.9.24",
|
||||||
"vconsole": "^3.15.1",
|
"vconsole": "^3.15.1",
|
||||||
"vue": "^3.3.0",
|
"vue": "^3.3.0",
|
||||||
"vue-router": "^4.2.0",
|
"vue-router": "^4.2.0"
|
||||||
"silly-datetime": "^0.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/cli": "8.3.0",
|
"@capacitor/cli": "8.3.0",
|
||||||
|
|||||||
@ -5,10 +5,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
withDefaults(
|
||||||
title: string
|
defineProps<{
|
||||||
text: string
|
title: string;
|
||||||
}>();
|
text?: string;
|
||||||
|
}>(),
|
||||||
|
{ text: '' },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.tips{
|
.tips{
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
我的检测报告
|
我的检测报告
|
||||||
</div>
|
</div>
|
||||||
<div class="r" @click="router.replace('/')"><img src="@/assets/close.png" alt=""></div>
|
<div class="r" @click="router.replace('/')"><img ref="closeBtnEl" src="@/assets/close.png" alt=""></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="time"> 检测时间:<span>{{ creatTime }}</span></div>
|
<div class="time"> 检测时间:<span>{{ creatTime }}</span></div>
|
||||||
|
|
||||||
@ -209,11 +209,155 @@ import Tips from '@/components/Tips/index.vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, watch, nextTick } from 'vue';
|
||||||
import { format } from 'silly-datetime';
|
import { format } from 'silly-datetime';
|
||||||
|
import html2canvas from 'html2canvas';
|
||||||
import Item from '@/components/Item/index.vue';
|
import Item from '@/components/Item/index.vue';
|
||||||
import content5Status1 from '@/assets/images/content5-status1.png';
|
import content5Status1 from '@/assets/images/content5-status1.png';
|
||||||
import content5Status2 from '@/assets/images/content5-status2.png';
|
import content5Status2 from '@/assets/images/content5-status2.png';
|
||||||
import content5Status3 from '@/assets/images/content5-status3.png';
|
import content5Status3 from '@/assets/images/content5-status3.png';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
/** 报告页整页截图上传 */
|
||||||
|
const REPORT_UPLOAD_URL = 'https://webapi.hbnews.net:8000/gateway/coupon/common/upload';
|
||||||
|
const REPORT_IMAGE_BASE = 'https://webapi.hbnews.net:8000/gateway/coupon';
|
||||||
|
|
||||||
|
/** 最近一次截图的 blob: URL;上传结束后仍会保留,勿在 finally 里立刻 revoke,否则新标签页打不开 */
|
||||||
|
let lastScreenshotBlobUrl: string | null = null;
|
||||||
|
|
||||||
|
function revokeLastScreenshotBlobUrl() {
|
||||||
|
if (lastScreenshotBlobUrl) {
|
||||||
|
URL.revokeObjectURL(lastScreenshotBlobUrl);
|
||||||
|
lastScreenshotBlobUrl = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildReportImageUrl(fileName: string): string {
|
||||||
|
if (!fileName) return '';
|
||||||
|
if (/^https?:\/\//i.test(fileName)) return fileName;
|
||||||
|
const base = REPORT_IMAGE_BASE.replace(/\/$/, '');
|
||||||
|
const path = fileName.replace(/^\//, '');
|
||||||
|
return `${base}/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractUploadFileName(payload: unknown): string | null {
|
||||||
|
if (!payload || typeof payload !== 'object') return null;
|
||||||
|
const o = payload as Record<string, unknown>;
|
||||||
|
if (typeof o.fileName === 'string' && o.fileName) return o.fileName;
|
||||||
|
const data = o.data;
|
||||||
|
if (data && typeof data === 'object' && typeof (data as Record<string, unknown>).fileName === 'string') {
|
||||||
|
const fn = (data as Record<string, unknown>).fileName;
|
||||||
|
return typeof fn === 'string' && fn ? fn : null;
|
||||||
|
}
|
||||||
|
const result = o.result;
|
||||||
|
if (result && typeof result === 'object' && typeof (result as Record<string, unknown>).fileName === 'string') {
|
||||||
|
const fn = (result as Record<string, unknown>).fileName;
|
||||||
|
return typeof fn === 'string' && fn ? fn : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 截取当前报告根节点长图并 multipart 上传,返回可访问的图片完整 URL */
|
||||||
|
async function captureReportPageAndUpload(): Promise<string | null> {
|
||||||
|
console.log('[报告截图] 开始生成');
|
||||||
|
const el = rootEl.value;
|
||||||
|
if (!el) {
|
||||||
|
console.warn('[报告截图] rootEl 为空,跳过');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = scrollContainer.value;
|
||||||
|
let prevScroll = 0;
|
||||||
|
if (container === window) {
|
||||||
|
prevScroll = window.scrollY;
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
} else if (container instanceof HTMLElement) {
|
||||||
|
prevScroll = container.scrollTop;
|
||||||
|
container.scrollTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
drawRadarChart();
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabsNode = tabsEl.value;
|
||||||
|
const closeNode = closeBtnEl.value;
|
||||||
|
tabsNode?.classList.add('step4-capture-hide');
|
||||||
|
closeNode?.classList.add('step4-capture-hide');
|
||||||
|
await nextTick();
|
||||||
|
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const canvas = await html2canvas(el, {
|
||||||
|
scale: Math.min(window.devicePixelRatio || 1, 2),
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: false,
|
||||||
|
backgroundColor: '#F3F3F3',
|
||||||
|
width: el.scrollWidth,
|
||||||
|
height: el.scrollHeight,
|
||||||
|
windowWidth: el.scrollWidth,
|
||||||
|
windowHeight: el.scrollHeight,
|
||||||
|
scrollX: 0,
|
||||||
|
scrollY: 0,
|
||||||
|
logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = await new Promise<Blob>((resolve, reject) => {
|
||||||
|
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('截图生成失败'))), 'image/png', 0.92);
|
||||||
|
});
|
||||||
|
|
||||||
|
revokeLastScreenshotBlobUrl();
|
||||||
|
lastScreenshotBlobUrl = URL.createObjectURL(blob);
|
||||||
|
console.log('报告截图临时:', lastScreenshotBlobUrl);
|
||||||
|
const previewWin = window.open(lastScreenshotBlobUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
if (!previewWin) {
|
||||||
|
console.warn('弹窗被拦截时,请手动将控制台里的 blob: 整段地址粘贴到新标签页地址栏。');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessionStorage.getItem('step2_ark_result')) {
|
||||||
|
console.info('[报告截图] 无 step2_ark_result,仅生成临时 blob,跳过上传');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append('file', blob, `health-report-${Date.now()}.png`);
|
||||||
|
|
||||||
|
const res = await fetch(REPORT_UPLOAD_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await res.text();
|
||||||
|
let json: unknown;
|
||||||
|
try {
|
||||||
|
json = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
json = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('报告截图上传失败', res.status, text);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = extractUploadFileName(json);
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('报告截图上传响应无 fileName', json);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildReportImageUrl(fileName);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('报告截图或上传异常', e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
tabsNode?.classList.remove('step4-capture-hide');
|
||||||
|
closeNode?.classList.remove('step4-capture-hide');
|
||||||
|
if (container === window) window.scrollTo(0, prevScroll);
|
||||||
|
else if (container instanceof HTMLElement) container.scrollTop = prevScroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const creatTime = ref(new Date().toLocaleString());
|
const creatTime = ref(new Date().toLocaleString());
|
||||||
const data = ref<any>({});
|
const data = ref<any>({});
|
||||||
const summaryText = ref('');
|
const summaryText = ref('');
|
||||||
@ -259,6 +403,7 @@ const activeTab = ref(1);
|
|||||||
|
|
||||||
const rootEl = ref<HTMLElement | null>(null);
|
const rootEl = ref<HTMLElement | null>(null);
|
||||||
const tabsEl = ref<HTMLElement | null>(null);
|
const tabsEl = ref<HTMLElement | null>(null);
|
||||||
|
const closeBtnEl = ref<HTMLElement | null>(null);
|
||||||
const chartWrapEl = ref<HTMLElement | null>(null);
|
const chartWrapEl = ref<HTMLElement | null>(null);
|
||||||
const radarCanvasRef = ref<HTMLCanvasElement | null>(null);
|
const radarCanvasRef = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
@ -494,6 +639,13 @@ onMounted(() => {
|
|||||||
radarRo.observe(chartWrapEl.value);
|
radarRo.observe(chartWrapEl.value);
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', drawRadarChart);
|
window.addEventListener('resize', drawRadarChart);
|
||||||
|
|
||||||
|
// 进入报告页即生成截图并打印「报告截图临时」blob 地址;仅有 step2_ark_result 时才上传服务端
|
||||||
|
window.setTimeout(() => {
|
||||||
|
captureReportPageAndUpload().then((url) => {
|
||||||
|
if (url) console.log('报告截图已上传:', url);
|
||||||
|
});
|
||||||
|
}, 800);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -503,6 +655,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
revokeLastScreenshotBlobUrl();
|
||||||
const container = scrollContainer.value;
|
const container = scrollContainer.value;
|
||||||
if (container === window) window.removeEventListener('scroll', onScroll);
|
if (container === window) window.removeEventListener('scroll', onScroll);
|
||||||
else container.removeEventListener('scroll', onScroll);
|
else container.removeEventListener('scroll', onScroll);
|
||||||
@ -551,6 +704,10 @@ if (arkResult) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.step4-capture-hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.step4 {
|
.step4 {
|
||||||
background: #F3F3F3;
|
background: #F3F3F3;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user