Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
CFFP-HB
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Chao Sun
CFFP-HB
Commits
c9fc0671
Commit
c9fc0671
authored
Jan 16, 2026
by
Sweet Zhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
解决文件在手机上不能直接打开的问题
parent
1e4cf0b9
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
454 additions
and
680 deletions
+454
-680
components/bootpage/bootpage.vue
+1
-0
components/login/login.vue
+1
-0
components/pdf-viewer/pdf-viewer.vue
+425
-664
components/unipopup/loginPopup.vue
+1
-0
environments/environment.ts
+1
-1
myPackageA/compare-result/compare-result.vue
+20
-10
pages/index/index.vue
+1
-2
pages/personalCenter/detail.vue
+4
-3
No files found.
components/bootpage/bootpage.vue
View file @
c9fc0671
...
...
@@ -149,6 +149,7 @@
uni
.
setStorageSync
(
'isLogin'
,
'1'
);
uni
.
setStorageSync
(
'loginType'
,
'codelogin'
);
uni
.
setStorageSync
(
'cffp_userId'
,
this
.
userId
);
console
.
log
(
'============'
,
uni
.
getStorageSync
(
'cffp_userId'
))
uni
.
setStorageSync
(
'uni-token'
,
res
.
data
[
'token'
]);
this
.
loginTypeSync
=
"codelogin"
;
this
.
queryInfo
()
...
...
components/login/login.vue
View file @
c9fc0671
...
...
@@ -182,6 +182,7 @@
this
.
userId
=
String
(
res
[
'data'
][
'userId'
]);
uni
.
setStorageSync
(
'isLogin'
,
'1'
);
uni
.
setStorageSync
(
'cffp_userId'
,
this
.
userId
);
console
.
log
(
'============'
,
uni
.
getStorageSync
(
'cffp_userId'
))
uni
.
setStorageSync
(
'loginType'
,
this
.
loginType
);
uni
.
setStorageSync
(
'uni-token'
,
res
.
data
[
'token'
]);
...
...
components/pdf-viewer/pdf-viewer.vue
View file @
c9fc0671
<!-- components/pdf-viewer/PdfViewer.vue -->
<
template
>
<view
class=
"pdf-viewer"
ref=
"pdfContainerRef"
>
<!-- 横屏提示组件 -->
<LandscapeTip
:debug=
"false"
:auto-show=
"pdfInfo.landscapeFlag ? pdfInfo.landscapeFlag : false"
:show-delay=
"1000"
:check-wide-content=
"false"
/>
<!-- 添加一个滚动容器 -->
<view
class=
"pdf-viewer"
:style=
"
{ height: containerHeight + 'px' }">
<!-- 标题 -->
<view
v-if=
"pdfInfo.title"
class=
"pdf-header"
>
<text
class=
"pdf-title"
>
{{
pdfInfo
.
title
}}
</text>
</view>
<!-- 滚动容器 -->
<scroll-view
class=
"pdf-scroll-view
"
class=
"pdf-scroll
"
scroll-y
:show-scrollbar=
"false"
@
scroll=
"handle
Scroll"
@
scroll=
"on
Scroll"
:scroll-top=
"scrollTop"
:style=
"
{ height: scrollContainerHeight + 'px' }"
>
<!-- PDF文档信息 -->
<view
class=
"pdf-info"
v-if=
"pdfInfo.title"
>
<text
class=
"pdf-title"
>
{{
pdfInfo
.
title
}}
</text>
<text
class=
"pdf-page-count"
v-if=
"pdfPageCount > 0"
>
共
{{
pdfPageCount
}}
页
</text>
</view>
<!-- 页面列表 -->
<view
v-for=
"pageIndex in pdfPageCount"
:key=
"pageIndex"
class=
"page-container"
:id=
"`page-$
{pageIndex}`"
v-for=
"pageNum in totalPages"
:key=
"pageNum"
class=
"page-item"
:style=
"
{ minHeight: getPageMinHeight(pageNum) + 'px' }"
>
<view
class=
"page-header"
v-if=
"loadingStatus"
>
<text
class=
"page-number"
>
第
{{
pageIndex
}}
页
</text>
<text
class=
"page-status"
v-if=
"isPageLoading(pageIndex)"
>
加载中...
</text>
<text
class=
"page-status error"
v-else-if=
"isPageFailed(pageIndex)"
>
加载失败
</text>
<text
class=
"page-status success"
v-else-if=
"getPageImage(pageIndex)"
>
加载完成
</text>
<!-- 页码标签 -->
<view
v-if=
"props.showPageNumber"
class=
"page-number-tag"
>
第
{{
pageNum
}}
页
</view>
<view
class=
"page-content"
>
<view
class=
"loadEffect"
v-if=
"!getPageImage(pageIndex) || isPageLoading(pageIndex)"
></view
>
<!-- 缩小模式:widthFix + 固定最大高度防过长 --
>
<view
v-if=
"!isZoomed[pageNum] && hasImage(pageNum)"
class=
"fit-mode"
>
<image
v-if=
"getPageImage(pageIndex)"
:src=
"getPageImage(pageIndex)"
:src=
"getImage(pageNum)"
mode=
"widthFix"
class=
"pdf-image"
@
load=
"handlePageImageLoad(pageIndex)"
@
error=
"handlePageImageError(pageIndex)"
class=
"pdf-image-fit"
:show-menu-by-longpress=
"false"
></image>
<view
v-else-if=
"isPageFailed(pageIndex)"
class=
"page-error"
@
click=
"retryLoadPage(pageIndex)"
>
<text
class=
"error-text"
>
页面加载失败,点击重试
</text>
<text
class=
"retry-count"
>
已重试
{{
getPageRetryCount
(
pageIndex
)
}}
次
</text>
</view>
<view
v-else
class=
"page-placeholder"
>
<text>
页面加载中...
</text>
/>
</view>
<!-- 高清模式:原始尺寸 + 可拖动 -->
<view
v-else-if=
"isZoomed[pageNum] && hasImage(pageNum)"
class=
"zoom-container"
@
touchstart=
"onTouchStart($event, pageNum)"
@
touchmove=
"onTouchMove($event, pageNum)"
@
touchend=
"onTouchEnd"
@
touchcancel=
"onTouchEnd"
>
<view
class=
"image-original"
:style=
"
{
transform: `translate(${translateX[pageNum] || 0}px, ${translateY[pageNum] || 0}px)`,
width: (pageRenderWidth[pageNum] || 0) + 'px',
height: (pageRenderHeight[pageNum] || 0) + 'px'
}"
>
<image
:src=
"getImage(pageNum)"
mode=
"scaleToFill"
style=
"display: block; width: 100%; height: 100%;"
/>
</view>
</view>
<!-- 加载状态 -->
<view
v-if=
"loading"
class=
"loading-state"
>
<view
class=
"loading-spinner"
></view>
<text>
文件较大,正在加载中...
</text>
<!-- 加载中 / 错误 -->
<view
v-else-if=
"isLoading(pageNum)"
class=
"placeholder loading"
>
<view
class=
"spinner"
></view>
</view>
<view
v-else-if=
"isFailed(pageNum)"
class=
"placeholder error"
@
click=
"retryPage(pageNum)"
>
❌ 加载失败,点击重试
</view>
<!-- 错误状态 -->
<view
v-if=
"error"
class=
"error-state"
>
<text
class=
"error-icon"
>
❌
</text>
<text
class=
"error-message"
>
{{
errorMessage
}}
</text>
<button
class=
"retry-button"
@
click=
"initPdf"
>
重试
</button>
<!-- 操作按钮 -->
<view
class=
"action-btns"
v-if=
"hasImage(pageNum)"
>
<button
v-if=
"!isZoomed[pageNum]"
class=
"zoom-btn"
@
click=
"toggleZoom(pageNum)"
>
放大查看
</button>
<button
v-else
class=
"reset-btn-inline"
@
click=
"resetZoom(pageNum)"
>
重置
</button>
</view>
</view>
<!-- 加载进度 -->
<view
v-if=
"!loading && !error && pdfPageCount > 0"
class=
"progress-info"
>
<text>
已加载
{{
loadedPages
}}
/
{{
pdfPageCount
}}
页
</text>
<view
class=
"progress-bar"
>
<view
class=
"progress-inner"
:style=
"
{ width: `${(loadedPages / pdfPageCount) * 100}%` }">
</view>
<!-- 全局状态 -->
<view
v-if=
"globalLoading"
class=
"global-status"
>
<view
class=
"spinner"
></view>
<text>
正在加载文档...
</text>
</view>
<view
v-else-if=
"globalError"
class=
"global-status error"
>
<text>
❌
{{
errorMessage
}}
</text>
<button
size=
"mini"
@
click=
"reload"
>
重试
</button>
</view>
<view
v-else-if=
"totalPages > 0"
class=
"progress-bar"
>
<text>
已加载
{{
loadedSet
.
size
}}
/
{{
totalPages
}}
页
</text>
<view
class=
"bar-bg"
>
<view
class=
"bar-fill"
:style=
"
{ width: progress + '%' }">
</view>
</view>
</view>
</scroll-view>
...
...
@@ -82,778 +114,506 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
,
watch
,
onMounted
,
onUnmounted
,
nextTick
}
from
'vue'
;
// 导入本地安装的PDF.js
import
*
as
pdfjsLib
from
'pdfjs-dist'
;
// ================== IMPORTS ==================
import
{
ref
,
computed
,
onMounted
,
onUnmounted
}
from
'vue'
;
import
*
as
pdfjsLib
from
'pdfjs-dist/build/pdf'
;
// 👇 关键:静态导入 worker(Vite 语法)
import
pdfjsWorker
from
'pdfjs-dist/build/pdf.worker?url'
;
import
LandscapeTip
from
'@/components/LandscapeTip/LandscapeTip.vue'
;
// ==================
======== 类型定义 ========
==================
// ==================
PROPS
==================
interface
PdfInfo
{
title
?:
string
;
url
:
string
;
landscapeFlag
?:
boolean
;
}
interface
Props
{
const
props
=
withDefaults
(
defineProps
<
{
pdfInfo
:
PdfInfo
;
autoLoad
?:
boolean
;
lazyLoad
?:
boolean
;
maxRetryCount
?:
number
;
loadingStatus
?:
boolean
;
}
// ========================== Props & Emits ==========================
const
props
=
withDefaults
(
defineProps
<
Props
>
(),
{
showPageName
?:
boolean
;
showPageNumber
?:
boolean
;
}
>
(),
{
autoLoad
:
true
,
lazyLoad
:
true
,
maxRetryCount
:
3
,
loadingStatus
:
false
,
showPageNumber
:
false
,
});
const
emit
=
defineEmits
<
{
loadStart
:
[
url
:
string
];
loadComplete
:
[
url
:
string
,
pageCount
:
number
];
loadError
:
[
url
:
string
,
error
:
Error
];
pageChange
:
[
currentPage
:
number
,
totalPages
:
number
];
}
>
();
// ========================== 响应式数据 ==========================
const
pdfImages
=
ref
<
string
[]
>
([]);
const
imgLoading
=
ref
<
boolean
[]
>
([]);
const
pdfPageCount
=
ref
(
0
);
const
currentLoading
=
ref
(
0
);
const
pdfDoc
=
ref
<
any
>
(
null
);
const
failedPages
=
ref
<
Record
<
number
,
number
>>
({});
const
loadingQueue
=
ref
<
number
[]
>
([]);
const
isProcessingQueue
=
ref
(
false
);
const
loading
=
ref
(
false
);
const
error
=
ref
(
false
);
const
errorMessage
=
ref
(
''
);
const
currentPage
=
ref
(
1
);
const
lastScrollTime
=
ref
(
0
);
const
scrollThrottle
=
ref
(
300
);
const
loadedPageSet
=
ref
<
Set
<
number
>>
(
new
Set
());
// 记录已加载的页面
// 添加 scrollTop 用于控制滚动位置
// ================== STATE ==================
const
isMounted
=
ref
(
true
);
const
containerHeight
=
ref
(
0
);
const
scrollContainerHeight
=
ref
(
0
);
// PDF 文档
const
pdfDoc
=
ref
<
pdfjsLib
.
PDFDocumentProxy
|
null
>
(
null
);
const
totalPages
=
ref
(
0
);
// 页面数据(按页存储)
const
images
=
ref
<
string
[]
>
([]);
const
loading
=
ref
<
boolean
[]
>
([]);
const
failed
=
ref
<
Record
<
number
,
number
>>
({});
const
loadedSet
=
ref
<
Set
<
number
>>
(
new
Set
());
// 👇 每页独立尺寸(支持横版/竖版)
const
pageOriginalWidth
=
ref
<
Record
<
number
,
number
>>
({});
const
pageOriginalHeight
=
ref
<
Record
<
number
,
number
>>
({});
const
pageRenderWidth
=
ref
<
Record
<
number
,
number
>>
({});
const
pageRenderHeight
=
ref
<
Record
<
number
,
number
>>
({});
// 双模式状态
const
isZoomed
=
ref
<
Record
<
number
,
boolean
>>
({});
const
translateX
=
ref
<
Record
<
number
,
number
>>
({});
const
translateY
=
ref
<
Record
<
number
,
number
>>
({});
// 滚动 & 加载队列
const
scrollTop
=
ref
(
0
);
// ========================== 初始化PDF.js ==========================
// 设置worker
pdfjsLib
.
GlobalWorkerOptions
.
workerSrc
=
pdfjsWorker
;
// ========================== 横屏提示处理 ==========================
const
autoLandscapeTipRef
=
ref
();
/**
* 提示关闭回调
*/
const
onTipClose
=
()
=>
{
console
.
log
(
'横屏提示已关闭'
);
};
/**
* 提示显示回调
*/
const
onTipShow
=
()
=>
{
console
.
log
(
'横屏提示已显示'
);
};
const
lastScroll
=
ref
(
0
);
const
queue
=
ref
<
number
[]
>
([]);
const
isProcessingQueue
=
ref
(
false
);
/**
* 方向变化回调
*/
const
onOrientationChange
=
(
orientation
:
'portrait'
|
'landscape'
)
=>
{
console
.
log
(
'屏幕方向变为:'
,
orientation
);
};
// 全局状态
const
globalLoading
=
ref
(
false
);
const
globalError
=
ref
(
false
);
const
errorMessage
=
ref
(
''
);
// ================== COMPUTED ==================
const
progress
=
computed
(()
=>
totalPages
.
value
>
0
?
(
loadedSet
.
value
.
size
/
totalPages
.
value
)
*
100
:
0
);
// ========================== 计算属性 ==========================
const
loadedPages
=
computed
(()
=>
{
return
loadedPageSet
.
value
.
size
;
})
;
const
hasImage
=
(
pageNum
:
number
)
=>
!!
images
.
value
[
pageNum
-
1
];
const
getImage
=
(
pageNum
:
number
)
=>
images
.
value
[
pageNum
-
1
]
||
''
;
const
isLoading
=
(
pageNum
:
number
)
=>
!!
loading
.
value
[
pageNum
-
1
]
;
const
isFailed
=
(
pageNum
:
number
)
=>
(
failed
.
value
[
pageNum
]
||
0
)
>
0
;
const
hasMoreToLoad
=
computed
(()
=>
{
return
loadedPages
.
value
<
pdfPageCount
.
value
;
});
// ========================== 生命周期 ==========================
// ================== LIFECYCLE ==================
onMounted
(()
=>
{
if
(
props
.
autoLoad
)
{
initPdf
()
;
}
const
sys
=
uni
.
getSystemInfoSync
();
containerHeight
.
value
=
sys
.
windowHeight
;
scrollContainerHeight
.
value
=
sys
.
windowHeight
-
(
props
.
pdfInfo
.
title
?
80
:
40
);
if
(
props
.
autoLoad
)
init
();
});
onUnmounted
(()
=>
{
isMounted
.
value
=
false
;
cleanup
();
});
// ========================== 监听器 ==========================
watch
(()
=>
props
.
pdfInfo
.
url
,
(
newUrl
,
oldUrl
)
=>
{
if
(
newUrl
&&
newUrl
!==
oldUrl
)
{
resetState
();
initPdf
();
}
});
// ========================== 公共方法 ==========================
/**
* 初始化PDF
*/
const
initPdf
=
async
()
=>
{
if
(
!
props
.
pdfInfo
.
url
)
{
setError
(
'PDF URL不能为空'
);
return
;
}
// ================== INIT ==================
const
init
=
async
()
=>
{
if
(
!
props
.
pdfInfo
.
url
)
return
setError
(
'PDF URL 为空'
);
try
{
resetState
();
loading
.
value
=
true
;
error
.
value
=
false
;
emit
(
'loadStart'
,
props
.
pdfInfo
.
url
);
await
loadPdfDocument
();
// 初始加载前3页
const
initialPages
=
Math
.
min
(
3
,
pdfPageCount
.
value
);
console
.
log
(
`初始加载前
${
initialPages
}
页`
);
for
(
let
i
=
1
;
i
<=
initialPages
;
i
++
)
{
addToLoadingQueue
(
i
);
}
processLoadingQueue
();
// 延迟检查其他可见页面
nextTick
(()
=>
{
setTimeout
(()
=>
{
checkVisiblePages
();
},
800
);
});
reset
();
globalLoading
.
value
=
true
;
await
loadDocument
();
preloadInitialPages
();
}
catch
(
err
:
any
)
{
setError
(
'文件读取失败'
)
// setError(`PDF初始化失败: ${err.message}`);
emit
(
'loadError'
,
props
.
pdfInfo
.
url
,
err
);
setError
(
err
.
message
||
'加载失败'
);
}
finally
{
l
oading
.
value
=
false
;
globalL
oading
.
value
=
false
;
}
};
/**
* 重新加载PDF
*/
const
reload
=
()
=>
{
initPdf
();
};
// ========================== 内部方法 ==========================
/**
* 重置状态
*/
const
resetState
=
()
=>
{
pdfImages
.
value
=
[];
imgLoading
.
value
=
[];
pdfPageCount
.
value
=
0
;
currentLoading
.
value
=
0
;
failedPages
.
value
=
{};
loadingQueue
.
value
=
[];
isProcessingQueue
.
value
=
false
;
error
.
value
=
false
;
errorMessage
.
value
=
''
;
currentPage
.
value
=
1
;
loadedPageSet
.
value
.
clear
();
// ================== LOAD DOCUMENT ==================
const
loadDocument
=
async
()
=>
{
// ====== 平台差异化设置 worker ======
let
useWorkerFlag
=
false
;
if
(
pdfDoc
.
value
)
{
pdfDoc
.
value
.
destroy
();
pdfDoc
.
value
=
null
;
}
};
/**
* 清理资源
*/
const
cleanup
=
()
=>
{
if
(
pdfDoc
.
value
)
{
pdfDoc
.
value
.
destroy
();
pdfDoc
.
value
=
null
;
}
};
// #ifdef H5
pdfjsLib
.
GlobalWorkerOptions
.
workerSrc
=
pdfjsWorker
;
// ← 静态导入
useWorkerFlag
=
true
;
// #endif
/**
* 设置错误状态
*/
const
setError
=
(
message
:
string
)
=>
{
error
.
value
=
true
;
errorMessage
.
value
=
message
;
loading
.
value
=
false
;
};
// #ifndef H5
pdfjsLib
.
GlobalWorkerOptions
.
workerSrc
=
undefined
;
useWorkerFlag
=
false
;
// #endif
/**
* 加载PDF文档
*/
const
loadPdfDocument
=
async
():
Promise
<
number
>
=>
{
try
{
const
loadingTask
=
pdfjsLib
.
getDocument
({
const
doc
=
await
pdfjsLib
.
getDocument
({
url
:
props
.
pdfInfo
.
url
,
cMapUrl
:
'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.11.338/cmaps/'
,
cMapPacked
:
true
,
disableFontFace
:
true
,
useSystemFonts
:
true
,
isEvalSupported
:
false
,
});
useWorker
:
useWorkerFlag
,
}).
promise
;
pdfDoc
.
value
=
await
loadingTask
.
promise
;
pdfPageCount
.
value
=
pdfDoc
.
value
.
numPages
;
pdfDoc
.
value
=
doc
;
totalPages
.
value
=
doc
.
numPages
;
// 初始化数组
pdfImages
.
value
=
new
Array
(
pdfPageCount
.
value
).
fill
(
''
);
imgLoading
.
value
=
new
Array
(
pdfPageCount
.
value
).
fill
(
false
);
emit
(
'loadComplete'
,
props
.
pdfInfo
.
url
,
pdfPageCount
.
value
);
console
.
log
(
`PDF文档加载完成:
${
props
.
pdfInfo
.
url
}
, 共
${
pdfPageCount
.
value
}
页`
);
return
pdfPageCount
.
value
;
images
.
value
=
new
Array
(
doc
.
numPages
).
fill
(
''
);
loading
.
value
=
new
Array
(
doc
.
numPages
).
fill
(
false
);
};
}
catch
(
err
:
any
)
{
console
.
error
(
'PDF文档加载失败:'
,
err
);
throw
new
Error
(
`文档加载失败:
${
err
.
message
}
`
);
}
// ================== PAGE LOADING ==================
const
preloadInitialPages
=
()
=>
{
const
count
=
Math
.
min
(
3
,
totalPages
.
value
);
for
(
let
i
=
1
;
i
<=
count
;
i
++
)
addToQueue
(
i
);
processQueue
();
};
const
addToQueue
=
(
pageNum
:
number
)
=>
{
if
(
!
queue
.
value
.
includes
(
pageNum
))
queue
.
value
.
push
(
pageNum
);
};
/**
* 滚动处理 - 使用 scroll-view 的 scroll 事件
*/
const
handleScroll
=
(
e
:
any
)
=>
{
if
(
!
props
.
lazyLoad
||
loading
.
value
)
return
;
const
processQueue
=
async
()
=>
{
if
(
isProcessingQueue
.
value
||
queue
.
value
.
length
===
0
)
return
;
isProcessingQueue
.
value
=
true
;
const
now
=
Date
.
now
();
if
(
now
-
lastScrollTime
.
value
<
scrollThrottle
.
value
)
{
return
;
while
(
queue
.
value
.
length
>
0
)
{
const
pageNum
=
queue
.
value
.
shift
()
!
;
await
loadPage
(
pageNum
);
await
new
Promise
(
r
=>
setTimeout
(
r
,
10
));
}
lastScrollTime
.
value
=
now
;
// 使用防抖
clearTimeout
((
window
as
any
).
scrollTimer
);
(
window
as
any
).
scrollTimer
=
setTimeout
(()
=>
{
checkVisiblePages
(
e
.
detail
.
scrollTop
);
},
100
);
isProcessingQueue
.
value
=
false
;
};
/**
* 检查可见页面 - 修改为接收 scrollTop 参数
*/
const
checkVisiblePages
=
(
scrollTop
:
number
)
=>
{
if
(
pdfPageCount
.
value
===
0
||
loadingQueue
.
value
.
length
>
5
)
return
;
console
.
log
(
'开始检查可见页面...'
,
scrollTop
);
const
loadPage
=
async
(
pageNum
:
number
)
=>
{
if
(
!
isMounted
.
value
||
!
pdfDoc
.
value
||
loadedSet
.
value
.
has
(
pageNum
))
return
;
if
(
getRetryCount
(
pageNum
)
>=
props
.
maxRetryCount
)
return
;
const
windowHeight
=
uni
.
getSystemInfoSync
().
windowHeight
;
try
{
loading
.
value
[
pageNum
-
1
]
=
true
;
const
page
=
await
pdfDoc
.
value
.
getPage
(
pageNum
);
// 计算可见区域
const
visibleTop
=
scrollTop
-
500
;
// 提前500px开始加载
const
visibleBottom
=
scrollTop
+
windowHeight
+
1000
;
// 延后1000px加载
// 获取原始尺寸
const
originalViewport
=
page
.
getViewport
({
scale
:
1
});
pageOriginalWidth
.
value
[
pageNum
]
=
originalViewport
.
width
;
pageOriginalHeight
.
value
[
pageNum
]
=
originalViewport
.
height
;
// 检查每个页面是否在可见区域内
for
(
let
i
=
1
;
i
<=
pdfPageCount
.
value
;
i
++
)
{
// 如果页面已经加载或正在加载,跳过
if
(
loadedPageSet
.
value
.
has
(
i
)
||
isPageLoading
(
i
))
{
continue
;
}
// 计算高清渲染尺寸(目标宽度 ~1600px)
const
targetPhysicalWidth
=
1600
;
const
scale
=
Math
.
min
(
5.0
,
Math
.
max
(
1.0
,
targetPhysicalWidth
/
originalViewport
.
width
));
const
renderViewport
=
page
.
getViewport
({
scale
});
// 检查页面位置
uni
.
createSelectorQuery
()
.
select
(
`#page-
${
i
}
`
)
.
boundingClientRect
((
rect
:
any
)
=>
{
if
(
rect
)
{
const
pageTop
=
scrollTop
+
rect
.
top
;
const
pageBottom
=
scrollTop
+
rect
.
bottom
;
// 如果页面在可见区域内
if
(
pageBottom
>
visibleTop
&&
pageTop
<
visibleBottom
)
{
console
.
log
(
`页面
${
i
}
在可见区域内,准备加载`
);
addToLoadingQueue
(
i
);
const
canvas
=
document
.
createElement
(
'canvas'
);
const
ctx
=
canvas
.
getContext
(
'2d'
)
!
;
canvas
.
width
=
renderViewport
.
width
;
canvas
.
height
=
renderViewport
.
height
;
await
page
.
render
({
canvasContext
:
ctx
,
viewport
:
renderViewport
}).
promise
;
// 存储渲染结果
pageRenderWidth
.
value
[
pageNum
]
=
canvas
.
width
;
pageRenderHeight
.
value
[
pageNum
]
=
canvas
.
height
;
images
.
value
[
pageNum
-
1
]
=
canvas
.
toDataURL
(
'image/jpeg'
,
0.95
);
loadedSet
.
value
.
add
(
pageNum
);
delete
failed
.
value
[
pageNum
];
canvas
.
width
=
canvas
.
height
=
0
;
}
catch
(
err
)
{
console
.
error
(
`Page
${
pageNum
}
error:`
,
err
);
failed
.
value
[
pageNum
]
=
(
failed
.
value
[
pageNum
]
||
0
)
+
1
;
}
finally
{
if
(
isMounted
.
value
)
loading
.
value
[
pageNum
-
1
]
=
false
;
}
};
// 更新当前页
if
(
rect
.
top
<
windowHeight
/
2
&&
rect
.
bottom
>
windowHeight
/
2
)
{
if
(
currentPage
.
value
!==
i
)
{
currentPage
.
value
=
i
;
emit
(
'pageChange'
,
i
,
pdfPageCount
.
value
);
}
}
}
})
.
exec
();
}
// ================== ZOOM MODE ==================
const
toggleZoom
=
(
pageNum
:
number
)
=>
{
if
(
!
hasImage
(
pageNum
))
return
;
isZoomed
.
value
[
pageNum
]
=
true
;
};
// 处理加载队列
setTimeout
(()
=>
{
processLoadingQueue
()
;
},
50
)
;
const
resetZoom
=
(
pageNum
:
number
)
=>
{
isZoomed
.
value
[
pageNum
]
=
false
;
translateX
.
value
[
pageNum
]
=
0
;
translateY
.
value
[
pageNum
]
=
0
;
};
/**
* 添加到加载队列
*/
const
addToLoadingQueue
=
(
pageNumber
:
number
)
=>
{
if
(
!
loadingQueue
.
value
.
includes
(
pageNumber
)
&&
!
loadedPageSet
.
value
.
has
(
pageNumber
)
&&
!
isPageLoading
(
pageNumber
))
{
console
.
log
(
`添加页面
${
pageNumber
}
到加载队列`
);
loadingQueue
.
value
.
push
(
pageNumber
);
// 限制队列长度,避免一次性加载太多
if
(
loadingQueue
.
value
.
length
>
10
)
{
loadingQueue
.
value
=
loadingQueue
.
value
.
slice
(
0
,
10
);
}
}
// ================== DRAGGING ==================
const
onTouchStart
=
(
e
:
any
,
pageNum
:
number
)
=>
{
const
touches
=
e
.
touches
||
[];
if
(
touches
.
length
!==
1
)
return
;
const
x
=
touches
[
0
].
clientX
-
(
translateX
.
value
[
pageNum
]
||
0
);
const
y
=
touches
[
0
].
clientY
-
(
translateY
.
value
[
pageNum
]
||
0
);
(
window
as
any
).
pdfTouchStart
=
{
x
,
y
,
pageNum
};
};
/**
* 处理加载队列
*/
const
processLoadingQueue
=
async
()
=>
{
if
(
isProcessingQueue
.
value
||
loadingQueue
.
value
.
length
===
0
)
return
;
const
onTouchMove
=
(
e
:
any
,
pageNum
:
number
)
=>
{
const
touches
=
e
.
touches
||
[];
if
(
touches
.
length
!==
1
||
!
isZoomed
.
value
[
pageNum
])
return
;
isProcessingQueue
.
value
=
true
;
const
start
=
(
window
as
any
).
pdfTouchStart
;
if
(
!
start
||
start
.
pageNum
!==
pageNum
)
return
;
try
{
// 每次处理1页
const
pagesToLoad
=
loadingQueue
.
value
.
splice
(
0
,
1
);
console
.
log
(
`处理加载队列: 加载页面
${
pagesToLoad
[
0
]}
`
);
const
currentX
=
touches
[
0
].
clientX
-
start
.
x
;
const
currentY
=
touches
[
0
].
clientY
-
start
.
y
;
for
(
const
pageNumber
of
pagesToLoad
)
{
await
loadPdfPage
(
pageNumber
);
}
const
sys
=
uni
.
getSystemInfoSync
();
const
viewW
=
sys
.
windowWidth
;
const
viewH
=
sys
.
windowHeight
;
const
imgW
=
pageRenderWidth
.
value
[
pageNum
]
||
0
;
const
imgH
=
pageRenderHeight
.
value
[
pageNum
]
||
0
;
}
catch
(
err
)
{
console
.
error
(
'处理加载队列失败:'
,
err
);
}
finally
{
isProcessingQueue
.
value
=
false
;
// 如果队列中还有任务,继续处理
if
(
loadingQueue
.
value
.
length
>
0
)
{
setTimeout
(
processLoadingQueue
,
200
);
}
else
{
// 队列处理完成后,再次检查可见页面
setTimeout
(()
=>
{
checkVisiblePages
();
},
300
);
}
}
};
if
(
imgW
===
0
||
imgH
===
0
)
return
;
/**
* 加载PDF页面
*/
const
loadPdfPage
=
async
(
pageNumber
:
number
)
=>
{
if
(
loadedPageSet
.
value
.
has
(
pageNumber
))
return
;
const
retryCount
=
getPageRetryCount
(
pageNumber
);
if
(
retryCount
>=
props
.
maxRetryCount
)
{
console
.
warn
(
`页面
${
pageNumber
}
已达到最大重试次数`
);
return
;
}
try
{
imgLoading
.
value
[
pageNumber
-
1
]
=
true
;
currentLoading
.
value
++
;
const
minX
=
viewW
-
imgW
>
0
?
0
:
viewW
-
imgW
;
const
minY
=
viewH
-
imgH
>
0
?
0
:
viewH
-
imgH
;
const
maxX
=
0
;
const
maxY
=
0
;
console
.
log
(
`开始加载页面
${
pageNumber
}
...`
);
translateX
.
value
[
pageNum
]
=
Math
.
min
(
maxX
,
Math
.
max
(
minX
,
currentX
));
translateY
.
value
[
pageNum
]
=
Math
.
min
(
maxY
,
Math
.
max
(
minY
,
currentY
));
};
if
(
!
pdfDoc
.
value
)
{
throw
new
Error
(
'PDF文档未加载'
)
;
}
const
onTouchEnd
=
()
=>
{
delete
(
window
as
any
).
pdfTouchStart
;
};
const
page
=
await
pdfDoc
.
value
.
getPage
(
pageNumber
);
// 根据设备像素比动态设置缩放
const
pixelRatio
=
window
.
devicePixelRatio
||
1
;
const
scale
=
Math
.
max
(
1.5
,
pixelRatio
);
// 至少 1.5 倍,高分屏自动更高
// ================== LAZY LOAD ==================
const
onScroll
=
(
e
:
any
)
=>
{
if
(
!
props
.
lazyLoad
||
globalLoading
.
value
)
return
;
scrollTop
.
value
=
e
.
detail
.
scrollTop
;
const
now
=
Date
.
now
();
if
(
now
-
lastScroll
.
value
<
200
)
return
;
lastScroll
.
value
=
now
;
checkVisible
();
};
const
viewport
=
page
.
getViewport
({
scale
});
// const viewport = page.getViewport({ scale: 1.8 });
const
canvas
=
document
.
createElement
(
'canvas'
);
const
context
=
canvas
.
getContext
(
'2d'
);
if
(
!
context
)
{
throw
new
Error
(
'无法获取Canvas上下文'
);
}
canvas
.
width
=
viewport
.
width
;
canvas
.
height
=
viewport
.
height
;
const
renderContext
=
{
canvasContext
:
context
,
viewport
:
viewport
};
await
page
.
render
(
renderContext
).
promise
;
const
imageData
=
canvas
.
toDataURL
(
'image/jpeg'
,
0.85
);
pdfImages
.
value
[
pageNumber
-
1
]
=
imageData
;
loadedPageSet
.
value
.
add
(
pageNumber
);
console
.
log
(
`页面
${
pageNumber
}
加载完成,当前已加载:
${
Array
.
from
(
loadedPageSet
.
value
).
join
(
','
)}
`
);
const
checkVisible
=
()
=>
{
if
(
totalPages
.
value
===
0
)
return
;
// 清理
canvas
.
width
=
0
;
canvas
.
height
=
0
;
const
sys
=
uni
.
getSystemInfoSync
();
const
winHeight
=
sys
.
windowHeight
;
const
top
=
scrollTop
.
value
;
// 清除失败记录
if
(
failedPages
.
value
[
pageNumber
])
{
delete
failedPages
.
value
[
pageNumber
];
}
// 👇 关键:不依赖页面高度,直接按页码区间预加载
// 假设每页至少占 200px(保守值)
const
MIN_PAGE_HEIGHT
=
200
;
// px
}
catch
(
err
:
any
)
{
console
.
error
(
`页面
${
pageNumber
}
加载失败:`
,
err
);
const
visibleStartPage
=
Math
.
max
(
1
,
Math
.
floor
(
top
/
MIN_PAGE_HEIGHT
));
const
visibleEndPage
=
Math
.
min
(
totalPages
.
value
,
Math
.
ceil
((
top
+
winHeight
*
2
)
/
MIN_PAGE_HEIGHT
)
);
// 记录失败次数
if
(
!
failedPages
.
value
[
pageNumber
])
{
failedPages
.
value
[
pageNumber
]
=
1
;
}
else
{
failedPages
.
value
[
pageNumber
]
++
;
}
// 预加载前后各 2 页(共约 5~7 页)
const
startPage
=
Math
.
max
(
1
,
visibleStartPage
-
2
);
const
endPage
=
Math
.
min
(
totalPages
.
value
,
visibleEndPage
+
2
);
// 对有问题的页面生成占位图
if
(
err
.
message
.
includes
(
'private field'
)
||
err
.
message
.
includes
(
'TypeError'
))
{
pdfImages
.
value
[
pageNumber
-
1
]
=
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjUwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjhmOWZhIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPuW3suino+eggTwvdGV4dD48L3N2Zz4='
;
loadedPageSet
.
value
.
add
(
pageNumber
);
for
(
let
i
=
startPage
;
i
<=
endPage
;
i
++
)
{
if
(
!
loadedSet
.
value
.
has
(
i
)
&&
!
isLoading
(
i
)
&&
!
isFailed
(
i
))
{
addToQueue
(
i
);
}
}
finally
{
imgLoading
.
value
[
pageNumber
-
1
]
=
false
;
currentLoading
.
value
--
;
}
};
/**
* 手动加载下一页
*/
const
loadNextPage
=
()
=>
{
if
(
pdfPageCount
.
value
===
0
)
return
;
// 找到第一个未加载的页面
for
(
let
i
=
1
;
i
<=
pdfPageCount
.
value
;
i
++
)
{
if
(
!
loadedPageSet
.
value
.
has
(
i
)
&&
!
isPageLoading
(
i
))
{
console
.
log
(
`手动加载页面
${
i
}
`
);
addToLoadingQueue
(
i
);
processLoadingQueue
();
break
;
}
if
(
queue
.
value
.
length
>
0
&&
!
isProcessingQueue
.
value
)
{
processQueue
();
}
};
/**
* 重试加载页面
*/
const
retryLoadPage
=
(
pageNumber
:
number
)
=>
{
const
retryCount
=
getPageRetryCount
(
pageNumber
);
if
(
retryCount
>=
props
.
maxRetryCount
)
{
uni
.
showToast
({
title
:
'已达到最大重试次数'
,
icon
:
'none'
,
duration
:
2000
});
return
;
}
if
(
failedPages
.
value
[
pageNumber
])
{
delete
failedPages
.
value
[
pageNumber
];
// ================== UTILS ==================
const
retryPage
=
(
pageNum
:
number
)
=>
{
if
(
getRetryCount
(
pageNum
)
<
props
.
maxRetryCount
)
{
addToQueue
(
pageNum
);
processQueue
();
}
// 从已加载集合中移除
loadedPageSet
.
value
.
delete
(
pageNumber
);
pdfImages
.
value
[
pageNumber
-
1
]
=
''
;
addToLoadingQueue
(
pageNumber
);
processLoadingQueue
();
};
// ========================== 辅助方法 ==========================
const
getPageImage
=
(
pageIndex
:
number
):
string
=>
{
return
pdfImages
.
value
[
pageIndex
-
1
]
||
''
;
};
const
isPageLoading
=
(
pageIndex
:
number
):
boolean
=>
{
return
imgLoading
.
value
[
pageIndex
-
1
]
||
false
;
};
const
isPageFailed
=
(
pageIndex
:
number
):
boolean
=>
{
const
retryCount
=
getPageRetryCount
(
pageIndex
);
return
retryCount
>
0
&&
retryCount
<=
props
.
maxRetryCount
;
const
getRetryCount
=
(
pageNum
:
number
)
=>
failed
.
value
[
pageNum
]
||
0
;
const
reload
=
()
=>
init
();
const
reset
=
()
=>
{
images
.
value
=
[];
loading
.
value
=
[];
failed
.
value
=
{};
loadedSet
.
value
.
clear
();
queue
.
value
=
[];
isZoomed
.
value
=
{};
translateX
.
value
=
{};
translateY
.
value
=
{};
pageOriginalWidth
.
value
=
{};
pageOriginalHeight
.
value
=
{};
pageRenderWidth
.
value
=
{};
pageRenderHeight
.
value
=
{};
globalError
.
value
=
false
;
errorMessage
.
value
=
''
;
};
const
getPageRetryCount
=
(
pageIndex
:
number
):
number
=>
{
return
failedPages
.
value
[
pageIndex
]
||
0
;
const
cleanup
=
()
=>
{
if
(
pdfDoc
.
value
)
{
pdfDoc
.
value
.
destroy
();
pdfDoc
.
value
=
null
;
}
};
const
handlePageImageLoad
=
(
pageIndex
:
number
)
=>
{
console
.
log
(
`页面
${
pageIndex
}
图片加载完成`
);
const
setError
=
(
msg
:
string
)
=>
{
globalError
.
value
=
true
;
errorMessage
.
value
=
msg
;
};
const
handlePageImageError
=
(
pageIndex
:
number
)
=>
{
console
.
error
(
`页面
${
pageIndex
}
图片加载失败`
);
const
getPageMinHeight
=
(
pageNum
:
number
)
=>
{
if
(
loadedSet
.
value
.
has
(
pageNum
))
{
const
w
=
pageRenderWidth
.
value
[
pageNum
]
||
1
;
const
h
=
pageRenderHeight
.
value
[
pageNum
]
||
1
;
return
uni
.
getSystemInfoSync
().
windowWidth
*
(
h
/
w
);
}
return
400
;
// px
};
//
暴露方法给父组件
//
================== EXPOSE ==================
defineExpose
({
initPdf
,
reload
,
loadNextPage
,
// 新增手动加载下一页方法
getCurrentPage
:
()
=>
currentPage
.
value
,
getTotalPages
:
()
=>
pdfPageCount
.
value
,
getLoadedPages
:
()
=>
Array
.
from
(
loadedPageSet
.
value
),
// 手动控制横屏提示
showLandscapeTip
:
()
=>
autoLandscapeTipRef
.
value
?.
show
?.(),
hideLandscapeTip
:
()
=>
autoLandscapeTipRef
.
value
?.
hide
?.(),
resetLandscapeTip
:
()
=>
autoLandscapeTipRef
.
value
?.
reset
?.()
init
,
});
</
script
>
<
style
scoped
lang=
"scss"
>
.pdf-viewer
{
width
:
100%
;
height
:
100vh
;
background
:
#ffffff
;
display
:
flex
;
flex-direction
:
column
;
}
.pdf-scroll-view
{
flex
:
1
;
height
:
0
;
//
重要:让
scroll-view
正确计算高度
background
:
#fff
;
}
.pdf-info
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
.pdf-header
{
padding
:
24
rpx
;
background
:
#f8f9fa
;
border-bottom
:
1
rpx
solid
#e8e8e8
;
.pdf-title
{
font-size
:
32
rpx
;
font-weight
:
600
;
color
:
#333
;
flex
:
1
;
}
.pdf-page-count
{
font-size
:
26
rpx
;
color
:
#666
;
background
:
#e6f7ff
;
padding
:
8
rpx
16
rpx
;
border-radius
:
20
rpx
;
}
font-weight
:
bold
;
}
.page-container
{
margin-bottom
:
32
rpx
;
border-bottom
:
1
rpx
solid
#f0f0f0
;
&:last-child
{
border-bottom
:
none
;
}
.pdf-scroll
{
width
:
100%
;
}
.page-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
20
rpx
24
rpx
;
background
:
#fafafa
;
.page-number
{
font-size
:
28
rpx
;
color
:
#333
;
font-weight
:
500
;
}
.page-item
{
position
:
relative
;
margin-bottom
:
10
rpx
;
padding
:
0
24
rpx
;
}
.page-status
{
.page-number-tag
{
position
:
absolute
;
top
:
-40
rpx
;
left
:
24
rpx
;
background
:
#20269B
;
color
:
white
;
padding
:
4
rpx
12
rpx
;
border-radius
:
20
rpx
;
font-size
:
24
rpx
;
&.success
{
color
:
#52c41a
;
}
&
.error
{
color
:
#ff4d4f
;
}
}
z-index
:
10
;
}
.page-content
{
position
:
relative
;
min-height
:
400
rpx
;
.pdf-image
{
.pdf-image-fit
{
width
:
100%
;
height
:
auto
;
display
:
block
;
//
禁用长按菜单
-webkit-touch-callout
:
none
;
-webkit-user-select
:
none
;
user-select
:
none
;
//
禁止长按保存
pointer-events
:
none
;
}
border-radius
:
12
rpx
;
box-shadow
:
0
4
rpx
12
rpx
rgba
(
0
,
0
,
0
,
0.05
);
}
.loadEffect
{
width
:
200
rpx
;
height
:
200
rpx
;
position
:
absolute
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
background
:
url('../../static/range-fullloading/loading.gif')
no-repeat
center
;
background-size
:
contain
;
z-index
:
10
;
}
.page-error
{
.placeholder
{
width
:
100%
;
height
:
400
rpx
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
height
:
400
rpx
;
background
:
#fff2f2
;
border
:
1
rpx
dashed
#ff4d4f
;
border-radius
:
8
rpx
;
color
:
#ff4d4f
;
.error-text
{
align-items
:
center
;
background
:
#f9f9f9
;
border-radius
:
12
rpx
;
font-size
:
28
rpx
;
margin-bottom
:
16
rpx
;
}
.retry-count
{
font-size
:
24
rpx
;
color
:
#999
;
}
}
.page-placeholder
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
height
:
400
rpx
;
background
:
#f8f9fa
;
color
:
#666
;
font-size
:
28
rpx
;
.placeholder.error
{
color
:
#ff4d4f
;
}
.
loading-state
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
80
rpx
0
;
.
zoom-container
{
overflow
:
hidden
;
background
:
#f9f9f9
;
border-radius
:
12
rpx
;
min-height
:
400
rpx
;
}
.loading-spinner
{
width
:
40
rpx
;
height
:
40
rpx
;
border
:
4
rpx
solid
#e8e8e8
;
border-top
:
4
rpx
solid
#20269B
;
border-radius
:
50%
;
animation
:
spin
1s
linear
infinite
;
margin-bottom
:
24
rpx
;
}
.image-original
{
position
:
relative
;
transition
:
transform
0.1s
ease-out
;
}
text
{
color
:
#666
;
font-size
:
28
rpx
;
}
.reset-btn
{
position
:
absolute
;
bottom
:
20
rpx
;
right
:
20
rpx
;
background
:
rgba
(
0
,
0
,
0
,
0.7
);
color
:
white
;
padding
:
12
rpx
20
rpx
;
border-radius
:
6
rpx
;
font-size
:
24
rpx
;
z-index
:
999
;
}
.error-state
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
80
rpx
0
;
.global-status
{
text-align
:
center
;
.error-icon
{
font-size
:
60
rpx
;
margin-bottom
:
24
rpx
;
}
.error-message
{
color
:
#ff4d4f
;
font-size
:
28
rpx
;
margin-bottom
:
32
rpx
;
padding
:
80
rpx
0
;
color
:
#666
;
.spinner
{
margin
:
0
auto
16
rpx
;
}
.retry-button
{
}
.global-status.error
button
{
margin-top
:
20
rpx
;
background
:
#20269B
;
color
:
white
;
border
:
none
;
padding
:
16
rpx
32
rpx
;
border-radius
:
8
rpx
;
font-size
:
28
rpx
;
}
}
.progress-info
{
padding
:
12
rpx
;
background
:
#f8f9fa
;
.progress-bar
{
padding
:
20
rpx
;
text-align
:
center
;
text
{
display
:
block
;
color
:
#666
;
font-size
:
26
rpx
;
margin-bottom
:
16
rpx
;
}
}
.progress-bar
{
color
:
#666
;
.bar-bg
{
width
:
100%
;
height
:
8
rpx
;
background
:
#e8e8e8
;
background
:
#eee
;
border-radius
:
4
rpx
;
overflow
:
hidden
;
.progress-inner
{
margin-top
:
8
rpx
;
.bar-fill
{
height
:
100%
;
background
:
#20269B
;
transition
:
width
0.3s
ease
;
transition
:
width
0.3s
;
}
}
}
.action-buttons
{
.spinner
{
width
:
40
rpx
;
height
:
40
rpx
;
border
:
4
rpx
solid
#eee
;
border-top
:
4
rpx
solid
#20269B
;
border-radius
:
50%
;
animation
:
spin
1s
linear
infinite
;
}
.action-btns
{
display
:
flex
;
justify-content
:
space-between
;
padding
:
20
rpx
16
rpx
;
background
:
#f8f9fa
;
border-top
:
1
rpx
solid
#e8e8e8
;
justify-content
:
center
;
margin-top
:
20
rpx
;
}
.action-btn
{
flex
:
1
;
margin
:
0
8
rpx
;
.zoom-btn
,
.reset-btn-inline
{
padding
:
8
rpx
24
rpx
;
font-size
:
24
rpx
;
border-radius
:
8
rpx
;
background
:
#20269B
;
color
:
white
;
border
:
none
;
padding
:
16
rpx
;
border-radius
:
8
rpx
;
font-size
:
26
rpx
;
line-height
:
1
;
}
&:disabled
{
background
:
#ccc
;
color
:
#999
;
}
}
.reset-btn-inline
{
background
:
#666
;
}
@keyframes
spin
{
to
{
transform
:
rotate
(
360deg
);
}
to
{
transform
:
rotate
(
360deg
);
}
}
</
style
>
\ No newline at end of file
components/unipopup/loginPopup.vue
View file @
c9fc0671
...
...
@@ -107,6 +107,7 @@
uni
.
setStorageSync
(
'isLogin'
,
'1'
);
uni
.
setStorageSync
(
'loginType'
,
'codelogin'
);
uni
.
setStorageSync
(
'cffp_userId'
,
res
.
data
[
'userId'
]);
console
.
log
(
'============'
,
uni
.
getStorageSync
(
'cffp_userId'
))
uni
.
setStorageSync
(
'uni-token'
,
res
.
data
[
'token'
]);
//关闭弹窗
this
.
$refs
.
loginPopup
.
close
();
...
...
environments/environment.ts
View file @
c9fc0671
...
...
@@ -53,7 +53,7 @@ const config = {
stage
,
prod
}
let
env
=
'
prod
'
;
let
env
=
'
dev
'
;
let
baseURL
=
config
[
env
].
base_url
;
let
apiURL
=
config
[
env
].
api_url
;
...
...
myPackageA/compare-result/compare-result.vue
View file @
c9fc0671
...
...
@@ -98,6 +98,8 @@
</view>
<!-- 放在
</view>
最后,
</
template
>
之前 -->
<!-- PDF 查看弹窗 -->
<!-- 调试用 -->
<view
v-if=
"showPdfModal"
>
Debug URL: {{ currentPdfUrl }}
</view>
<uni-popup
ref=
"pdfPopupRef"
:mask-click=
"true"
...
...
@@ -111,14 +113,15 @@
</view>
<!-- PDF 查看器 -->
<view
class=
"pdf-viewer-wrapper"
v-if=
"showPdfModa
l"
>
<view
class=
"pdf-viewer-wrapper"
v-if=
"showPdfModal && currentPdfUr
l"
>
<PdfViewer
:pdfInfo=
"{ url: currentPdfUrl }"
:autoLoad=
"true"
:lazyLoad=
"tru
e"
:lazyLoad=
"fals
e"
:maxRetryCount=
"2"
:loadingStatus=
"true"
style=
"height: 100%; width: 100%;"
@
loadComplete=
"handlePdfLoadComplete"
@
loadError=
"handlePdfLoadError"
@
pageChange=
"handlePageChange"
/>
</view>
</view>
...
...
@@ -130,11 +133,10 @@ import { ref, computed, onMounted } from 'vue';
import
{
useRouter
,
useRoute
}
from
'vue-router'
;
import
common
from
'@/common/common'
;
import
api
from
'@/api/api'
;
import
{
onBeforeUnmount
}
from
'vue'
;
// 导入PDF查看器组件
import
PdfViewer
from
'@/components/pdf-viewer/pdf-viewer.vue'
;
import
{
onBeforeUnmount
}
from
'vue'
;
import
uniPopup
from
'@dcloudio/uni-ui/lib/uni-popup/uni-popup.vue'
;
const
pdfPopupRef
=
ref
();
// 路由实例
const
router
=
useRouter
();
...
...
@@ -294,16 +296,21 @@ const navigateToPKPage = () => {
const
showPdfModal
=
ref
(
false
);
const
currentPdfUrl
=
ref
(
''
);
// 修改 getUrl 方法
const
getUrl
=
(
fileUrl
)
=>
{
if
(
!
fileUrl
)
{
uni
.
showToast
({
title
:
'暂无文档'
,
icon
:
'none'
});
return
;
}
uni
.
showLoading
({
title
:
'加载PDF中...'
});
// 所有平台统一处理:弹出 PdfViewer
uni
.
showLoading
({
title
:
'加载PDF中...'
});
currentPdfUrl
.
value
=
fileUrl
;
showPdfModal
.
value
=
true
;
console
.
log
(
'Opening PDF:'
,
fileUrl
);
// 延迟打开弹窗,确保数据已绑定
setTimeout
(()
=>
{
pdfPopupRef
.
value
?.
open
?.();
uni
.
hideLoading
();
},
100
);
};
// 关闭弹窗
...
...
@@ -587,6 +594,9 @@ onMounted(() => {
border-radius
:
50%
;
border
:
none
;
color
:
#999
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
}
.pdf-viewer-wrapper
{
...
...
pages/index/index.vue
View file @
c9fc0671
...
...
@@ -764,10 +764,9 @@
}
api
.
loginVerification
(
params
).
then
((
res
)
=>
{
if
(
res
[
'success'
]){
uni
.
setStorageSync
(
'isLogin'
,
'1'
);
uni
.
setStorageSync
(
'loginType'
,
'codelogin'
);
uni
.
setStorageSync
(
'cffp_userId'
,
res
.
data
.
userId
);
uni
.
setStorageSync
(
'cffp_userId'
,
JSON
.
stringify
(
res
.
data
.
userId
)
);
uni
.
setStorageSync
(
'uni-token'
,
res
.
data
[
'token'
]);
this
.
userId
=
res
.
data
.
userId
this
.
querySystemMessage
()
...
...
pages/personalCenter/detail.vue
View file @
c9fc0671
...
...
@@ -130,7 +130,8 @@ const companyPdf = ref<PdfItem>({
// urls: Array.from({ length: 21 }, (_, i) =>
// `${OSS_BASE_URL}/public/company-intro_part${i + 1}.pdf`
// ),
urls
:
[
`
${
OSS_BASE_URL
}
/public/company-intro.pdf`
],
// urls: [`${OSS_BASE_URL}/public/company-intro.pdf`],
urls
:
[
`
${
OSS_BASE_URL
}
/wslucky/product/2025/06/24/31c164ac-565c-4990-a584-b5d4935840d0.pdf`
],
type
:
'showURL'
});
...
...
@@ -310,7 +311,7 @@ const switchTab = (index: number) => {
const
tabs
=
filteredCurrentTabs
.
value
;
if
(
index
<
0
||
index
>=
tabs
.
length
||
tabs
.
length
===
0
)
return
;
uni
.
showLoading
({
title
:
'切换中...'
})
;
loading
.
value
=
true
;
setTimeout
(()
=>
{
activeTab
.
value
=
index
;
...
...
@@ -318,7 +319,7 @@ const switchTab = (index: number) => {
uni
.
setStorageSync
(
'tabsIndex'
,
index
);
setTimeout
(()
=>
{
uni
.
hideLoading
()
;
loading
.
value
=
false
;
},
300
);
},
100
);
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment