|
@@ -1,7 +1,12 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <view class="task-detail-layout">
|
|
|
|
|
|
|
+ <view class="task-detail-layout" :class="{ 'task-detail-layout--export': isExportMode }">
|
|
|
<!-- #ifdef H5 -->
|
|
<!-- #ifdef H5 -->
|
|
|
- <wd-navbar fixed placeholder safeAreaInsetTop title="任务详情" />
|
|
|
|
|
|
|
+ <wd-navbar
|
|
|
|
|
+ :fixed="!isExportMode"
|
|
|
|
|
+ :placeholder="!isExportMode"
|
|
|
|
|
+ safeAreaInsetTop
|
|
|
|
|
+ title="任务详情"
|
|
|
|
|
+ />
|
|
|
<!-- #endif -->
|
|
<!-- #endif -->
|
|
|
|
|
|
|
|
<view class="task-detail-page">
|
|
<view class="task-detail-page">
|
|
@@ -91,6 +96,7 @@ const EXPORT_MAX_WAIT_MS = 18_000
|
|
|
const EXPORT_POLL_INTERVAL_MS = 120
|
|
const EXPORT_POLL_INTERVAL_MS = 120
|
|
|
const EXPORT_STABLE_MS = 300
|
|
const EXPORT_STABLE_MS = 300
|
|
|
const EXPORT_DOCUMENT_CLASS = 'task-detail-export'
|
|
const EXPORT_DOCUMENT_CLASS = 'task-detail-export'
|
|
|
|
|
+const EXPORT_HEIGHT_PROPERTY = '--task-detail-export-height'
|
|
|
const QR_IMAGE_SELECTOR = '.share-detail-qrcode__image'
|
|
const QR_IMAGE_SELECTOR = '.share-detail-qrcode__image'
|
|
|
|
|
|
|
|
const taskId = ref('')
|
|
const taskId = ref('')
|
|
@@ -111,6 +117,8 @@ let exportStartTs = 0
|
|
|
let exportReadyNotified = false
|
|
let exportReadyNotified = false
|
|
|
let exportFlowVersion = 0
|
|
let exportFlowVersion = 0
|
|
|
|
|
|
|
|
|
|
+const isExportMode = computed(() => Boolean(exportToken.value))
|
|
|
|
|
+
|
|
|
const isShareTaskDetail = (detail: TaskDetailResponse): detail is ShareTaskDetailResponse => {
|
|
const isShareTaskDetail = (detail: TaskDetailResponse): detail is ShareTaskDetailResponse => {
|
|
|
return SHARE_TASK_TYPES.has(String(detail.taskType))
|
|
return SHARE_TASK_TYPES.has(String(detail.taskType))
|
|
|
}
|
|
}
|
|
@@ -151,6 +159,38 @@ const getPageRoot = (): Element | null => {
|
|
|
return document.querySelector('.task-detail-page')
|
|
return document.querySelector('.task-detail-page')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const setExportDocumentMode = (enabled: boolean) => {
|
|
|
|
|
+ document.documentElement.classList.toggle(EXPORT_DOCUMENT_CLASS, enabled)
|
|
|
|
|
+ document.documentElement.style.removeProperty(EXPORT_HEIGHT_PROPERTY)
|
|
|
|
|
+ delete document.documentElement.dataset.taskDetailExportReady
|
|
|
|
|
+
|
|
|
|
|
+ if (enabled) window.scrollTo(0, 0)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Selenium 按 body.scrollHeight 调整浏览器窗口。导出前固定真实内容高度,避免窗口
|
|
|
|
|
+ * 调整后 vh 再次参与计算,造成页面高度漂移并重新出现内部滚动条。
|
|
|
|
|
+ */
|
|
|
|
|
+const freezeExportDocumentHeight = async () => {
|
|
|
|
|
+ if (!isExportMode.value) return
|
|
|
|
|
+
|
|
|
|
|
+ document.documentElement.style.removeProperty(EXPORT_HEIGHT_PROPERTY)
|
|
|
|
|
+ window.scrollTo(0, 0)
|
|
|
|
|
+ await waitForPaint()
|
|
|
|
|
+
|
|
|
|
|
+ const layout = document.querySelector<HTMLElement>('.task-detail-layout')
|
|
|
|
|
+ if (!layout) return
|
|
|
|
|
+
|
|
|
|
|
+ const layoutRect = layout.getBoundingClientRect()
|
|
|
|
|
+ const contentHeight = Math.ceil(
|
|
|
|
|
+ Math.max(layout.scrollHeight, layoutRect.height, layoutRect.bottom + window.scrollY)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ document.documentElement.style.setProperty(EXPORT_HEIGHT_PROPERTY, `${contentHeight}px`)
|
|
|
|
|
+ document.documentElement.dataset.taskDetailExportReady = 'true'
|
|
|
|
|
+ await waitForPaint()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const getQrNaturalWidth = (): number => {
|
|
const getQrNaturalWidth = (): number => {
|
|
|
const qrElement = getPageRoot()?.querySelector(QR_IMAGE_SELECTOR)
|
|
const qrElement = getPageRoot()?.querySelector(QR_IMAGE_SELECTOR)
|
|
|
const qrImage = qrElement?.matches('img')
|
|
const qrImage = qrElement?.matches('img')
|
|
@@ -259,6 +299,8 @@ const waitAndNotifyExportReady = async (flowVersion: number) => {
|
|
|
if (flowVersion !== exportFlowVersion) return
|
|
if (flowVersion !== exportFlowVersion) return
|
|
|
|
|
|
|
|
if (errorMessage.value || !taskDetail.value) {
|
|
if (errorMessage.value || !taskDetail.value) {
|
|
|
|
|
+ await freezeExportDocumentHeight()
|
|
|
|
|
+ if (flowVersion !== exportFlowVersion) return
|
|
|
notifyExportReadyOnce(false, false)
|
|
notifyExportReadyOnce(false, false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -286,6 +328,8 @@ const waitAndNotifyExportReady = async (flowVersion: number) => {
|
|
|
previousSnapshot = snapshot
|
|
previousSnapshot = snapshot
|
|
|
stableSince = Date.now()
|
|
stableSince = Date.now()
|
|
|
} else if (Date.now() - stableSince >= EXPORT_STABLE_MS) {
|
|
} else if (Date.now() - stableSince >= EXPORT_STABLE_MS) {
|
|
|
|
|
+ await freezeExportDocumentHeight()
|
|
|
|
|
+ if (flowVersion !== exportFlowVersion) return
|
|
|
notifyExportReadyOnce(true, false)
|
|
notifyExportReadyOnce(true, false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -298,6 +342,8 @@ const waitAndNotifyExportReady = async (flowVersion: number) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (flowVersion === exportFlowVersion) {
|
|
if (flowVersion === exportFlowVersion) {
|
|
|
|
|
+ await freezeExportDocumentHeight()
|
|
|
|
|
+ if (flowVersion !== exportFlowVersion) return
|
|
|
notifyExportReadyOnce(false, true)
|
|
notifyExportReadyOnce(false, true)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -349,7 +395,7 @@ onLoad((options: PageLoadOptions = {}) => {
|
|
|
taskId.value = String(options.id ?? options.taskId ?? '').trim()
|
|
taskId.value = String(options.id ?? options.taskId ?? '').trim()
|
|
|
exportToken.value = String(options.exportToken ?? '').trim()
|
|
exportToken.value = String(options.exportToken ?? '').trim()
|
|
|
// #ifdef H5
|
|
// #ifdef H5
|
|
|
- document.documentElement.classList.toggle(EXPORT_DOCUMENT_CLASS, Boolean(exportToken.value))
|
|
|
|
|
|
|
+ setExportDocumentMode(Boolean(exportToken.value))
|
|
|
// #endif
|
|
// #endif
|
|
|
exportStartTs = Date.now()
|
|
exportStartTs = Date.now()
|
|
|
exportReadyNotified = false
|
|
exportReadyNotified = false
|
|
@@ -382,26 +428,47 @@ onLoad((options: PageLoadOptions = {}) => {
|
|
|
onUnload(() => {
|
|
onUnload(() => {
|
|
|
exportFlowVersion += 1
|
|
exportFlowVersion += 1
|
|
|
// #ifdef H5
|
|
// #ifdef H5
|
|
|
- document.documentElement.classList.remove(EXPORT_DOCUMENT_CLASS)
|
|
|
|
|
|
|
+ setExportDocumentMode(false)
|
|
|
// #endif
|
|
// #endif
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
<style>
|
|
|
/* #ifdef H5 */
|
|
/* #ifdef H5 */
|
|
|
-/* 导出时让 body 的实际高度随完整内容增长,覆盖 uni-app 默认的 100% 高度链。 */
|
|
|
|
|
-html.task-detail-export,
|
|
|
|
|
-html.task-detail-export body,
|
|
|
|
|
-html.task-detail-export #app,
|
|
|
|
|
-html.task-detail-export uni-app,
|
|
|
|
|
-html.task-detail-export uni-page,
|
|
|
|
|
-html.task-detail-export uni-page-wrapper,
|
|
|
|
|
-html.task-detail-export uni-page-body {
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+ * 截图时只保留浏览器文档滚动,解除 uni-app 默认的 100% 高度链和内部滚动容器。
|
|
|
|
|
+ * 后端读取 body.scrollHeight 后即可得到完整内容高度。
|
|
|
|
|
+ */
|
|
|
|
|
+:global(html.task-detail-export) {
|
|
|
height: auto !important;
|
|
height: auto !important;
|
|
|
- min-height: 100vh;
|
|
|
|
|
|
|
+ min-height: 0 !important;
|
|
|
|
|
+ max-height: none !important;
|
|
|
|
|
+ overflow-x: hidden !important;
|
|
|
|
|
+ overflow-y: visible !important;
|
|
|
|
|
+ scrollbar-width: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:global(html.task-detail-export body),
|
|
|
|
|
+:global(html.task-detail-export #app),
|
|
|
|
|
+:global(html.task-detail-export uni-app),
|
|
|
|
|
+:global(html.task-detail-export uni-page),
|
|
|
|
|
+:global(html.task-detail-export uni-page-wrapper),
|
|
|
|
|
+:global(html.task-detail-export uni-page-body) {
|
|
|
|
|
+ height: auto !important;
|
|
|
|
|
+ min-height: 0 !important;
|
|
|
max-height: none !important;
|
|
max-height: none !important;
|
|
|
overflow: visible !important;
|
|
overflow: visible !important;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+:global(html.task-detail-export body) {
|
|
|
|
|
+ min-height: var(--task-detail-export-height, 0px) !important;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:global(html.task-detail-export::-webkit-scrollbar),
|
|
|
|
|
+:global(html.task-detail-export body::-webkit-scrollbar) {
|
|
|
|
|
+ width: 0;
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+}
|
|
|
/* #endif */
|
|
/* #endif */
|
|
|
</style>
|
|
</style>
|
|
|
|
|
|
|
@@ -422,6 +489,14 @@ html.task-detail-export uni-page-body {
|
|
|
.task-detail-page {
|
|
.task-detail-page {
|
|
|
min-height: calc(100vh - 44px);
|
|
min-height: calc(100vh - 44px);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.task-detail-layout--export,
|
|
|
|
|
+.task-detail-layout--export .task-detail-page {
|
|
|
|
|
+ height: auto !important;
|
|
|
|
|
+ min-height: 0 !important;
|
|
|
|
|
+ max-height: none !important;
|
|
|
|
|
+ overflow: visible !important;
|
|
|
|
|
+}
|
|
|
/* #endif */
|
|
/* #endif */
|
|
|
|
|
|
|
|
.task-detail-state {
|
|
.task-detail-state {
|