feat:职代会h5端文件预览兼容、增加切换届次功能

This commit is contained in:
2026-07-31 11:06:45 +08:00
parent cc71822aae
commit 5477355f64
10 changed files with 676 additions and 361 deletions
@@ -15,8 +15,12 @@ layout("/layouts/platform_h5.html"){
<div class="page-header">
<div class="session-info-card">
<h2 class="session-title">{{ selectedSessionName }}</h2>
<p class="session-subtitle">{{ selectedTimeText }}</p>
<!-- <p class="session-subtitle">{{ selectedTimeText }}</p>-->
</div>
<button v-if="timesList.length > 1" type="button" class="header-switch-btn" @click="showTimePicker = true">
<span>切换届次</span>
<van-icon name="arrow-down" size="12"></van-icon>
</button>
</div>
<div class="module-list-container">
@@ -41,12 +45,6 @@ layout("/layouts/platform_h5.html"){
</div>
</div>
<div v-if="timesList.length > 1" class="bottom-switch-bar">
<span class="switch-text" @click="showTimePicker = true">
<van-icon name="arrow-down" size="12"></van-icon>
切换届次
</span>
</div>
</template>
<van-popup v-model="showTimePicker" position="bottom">
@@ -69,6 +67,7 @@ layout("/layouts/platform_h5.html"){
loading: true,
timesList: [],
selectedTimeId: "",
selectedSessionId: "",
selectedTimeText: "",
selectedSessionName: "",
selectedRepId: "",
@@ -80,7 +79,7 @@ layout("/layouts/platform_h5.html"){
formattedTimeColumns() {
return this.timesList.map((item) => ({
text: item.name,
value: item.id
value: item.timeId || item.id || item.sessionId
}))
}
},
@@ -90,7 +89,8 @@ layout("/layouts/platform_h5.html"){
try {
await this.fetchRepTimes()
if (this.timesList.length > 0) {
this.selectTime(this.timesList[0])
const initialTimeId = this.initialTimeId()
this.selectTime(this.resolveInitialTime(initialTimeId), !!initialTimeId)
await this.fetchModules()
}
} finally {
@@ -108,6 +108,13 @@ layout("/layouts/platform_h5.html"){
this.timesList = res.data || []
}
},
initialTimeId() {
const params = new URLSearchParams(window.location.search)
return params.get("timeId") || params.get("sessionId") || ""
},
resolveInitialTime(timeId) {
return this.timesList.find((item) => this.sameTime(item, timeId)) || this.timesList[0]
},
async fetchModules() {
const res = await this.$axios.get("/platform/h5/congress/materialsTypeList")
if (res.code !== 0) return
@@ -125,19 +132,38 @@ layout("/layouts/platform_h5.html"){
desc: "点击进行会议签到"
}].concat(modules)
},
selectTime(item) {
this.selectedTimeId = item.id || item.timeId || ""
selectTime(item, syncUrl) {
this.selectedTimeId = item.timeId || item.id || item.sessionId || ""
this.selectedSessionId = item.sessionId || this.selectedTimeId
this.selectedTimeText = item.name || ""
this.selectedSessionName = item.sessionName || ""
this.selectedRepId = item.repId || ""
window.localStorage.setItem("congressTimeId", this.selectedTimeId)
window.localStorage.setItem("congressSessionId", this.selectedSessionId)
window.localStorage.setItem("timeName", this.selectedTimeText)
window.localStorage.setItem("sessionName", this.selectedSessionName)
window.localStorage.setItem("repId", this.selectedRepId)
if (syncUrl) {
this.syncSelectedTimeToUrl()
}
},
sameTime(item, timeId) {
if (!item || !timeId) return false
return item.id === timeId || item.timeId === timeId || item.sessionId === timeId
},
syncSelectedTimeToUrl() {
if (!this.selectedTimeId) return
const params = new URLSearchParams(window.location.search)
params.set("timeId", this.selectedTimeId)
const nextUrl = window.location.pathname + "?" + params.toString()
const state = Object.assign({}, window.history.state || {})
state.url = nextUrl
window.history.replaceState(state, document.title, nextUrl)
},
onTimeConfirm(value) {
const selected = this.timesList.find((item) => item.id === value.value)
const selected = this.timesList.find((item) => this.sameTime(item, value.value))
if (selected) {
this.selectTime(selected)
this.selectTime(selected, true)
this.fetchModules()
}
this.showTimePicker = false
@@ -148,13 +174,15 @@ layout("/layouts/platform_h5.html"){
return
}
if (module.id === "signin" || module.name.indexOf("会议签到") > -1) {
this.$pjaxReplace("/platform/h5/congress/meeting/list?moduleId=signin&moduleName=会议签到&timeId=" + encodeURIComponent(this.selectedTimeId))
this.$pjaxReplace("/platform/h5/congress/meeting/list?moduleId=signin&moduleName=会议签到&timeId=" + encodeURIComponent(this.selectedTimeId) +
"&sessionId=" + encodeURIComponent(this.selectedSessionId))
return
}
this.$pjaxReplace("/platform/h5/congress/materials/list?moduleId=" + encodeURIComponent(module.id) +
"&typeId=" + encodeURIComponent(module.id) +
"&moduleName=" + encodeURIComponent(module.name) +
"&timeId=" + encodeURIComponent(this.selectedTimeId))
"&timeId=" + encodeURIComponent(this.selectedTimeId) +
"&sessionId=" + encodeURIComponent(this.selectedSessionId))
},
resolveIcon(name) {
if (!name) return "orders-o"
@@ -191,12 +219,13 @@ layout("/layouts/platform_h5.html"){
.page-header {
background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%);
padding: 24px 16px 28px;
padding: 34px 16px 28px;
color: #fff;
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.15);
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.session-info-card {
@@ -221,6 +250,31 @@ layout("/layouts/platform_h5.html"){
margin: 0;
}
.header-switch-btn {
position: absolute;
top: 10px;
right: 12px;
height: 28px;
padding: 0 10px;
border: 0;
border-radius: 14px;
background: rgba(255, 255, 255, 0.18);
color: #fff;
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
line-height: 1;
cursor: pointer;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
backdrop-filter: blur(8px);
}
.header-switch-btn:active {
background: rgba(255, 255, 255, 0.28);
transform: scale(0.98);
}
.module-list-container {
padding: 16px;
}
@@ -327,22 +381,6 @@ layout("/layouts/platform_h5.html"){
transform: translateX(4px);
}
.bottom-switch-bar {
padding: 20px 16px calc(20px + env(safe-area-inset-bottom));
display: flex;
justify-content: center;
}
.switch-text {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: #969799;
cursor: pointer;
user-select: none;
}
.empty-box {
min-height: calc(100vh - 46px);
display: flex;
@@ -3,7 +3,7 @@ layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar :title="pageTitle" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-nav-bar :title="pageTitle" left-text="返回" left-arrow @click-left="backToCongress" placeholder fixed></van-nav-bar>
<div class="file-list-page">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
@@ -16,7 +16,7 @@ layout("/layouts/platform_h5.html"){
<div class="card-left">
<div class="icon-wrapper">
<van-icon :name="getFileIcon(item.fileType)" size="36"></van-icon>
<img :src="getFileIconUrl(item)" :alt="resolveFileSuffix(item) || 'file'" class="file-type-icon">
</div>
</div>
<div class="card-content">
@@ -51,7 +51,8 @@ layout("/layouts/platform_h5.html"){
refreshing: false,
loadedOnce: false,
typeId: "",
timeId: ""
timeId: "",
sessionId: ""
}
},
methods: {
@@ -60,6 +61,11 @@ layout("/layouts/platform_h5.html"){
this.pageTitle = params.get("moduleName") || "文件公告"
this.typeId = params.get("typeId") || params.get("moduleId") || ""
this.timeId = params.get("timeId") || ""
this.sessionId = params.get("sessionId") || this.timeId
},
backToCongress() {
const timeId = this.timeId || this.sessionId || window.localStorage.getItem("congressTimeId") || ""
this.$pjaxReplace("/platform/h5/congress" + (timeId ? "?timeId=" + encodeURIComponent(timeId) : ""))
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
@@ -95,16 +101,39 @@ layout("/layouts/platform_h5.html"){
this.refreshing = false
})
},
getFileIcon(mimeType) {
if (!mimeType) return "description"
const type = String(mimeType).toLowerCase()
if (type.indexOf("pdf") > -1) return "description"
if (type.indexOf("word") > -1) return "edit"
if (type.indexOf("excel") > -1 || type.indexOf("sheet") > -1) return "chart-trending-o"
if (type.indexOf("powerpoint") > -1 || type.indexOf("presentation") > -1) return "play-circle-o"
if (type.indexOf("image") > -1) return "photo"
if (type.indexOf("zip") > -1 || type.indexOf("rar") > -1) return "bag-o"
return "description"
getFileIconUrl(item) {
return this.$commonUtil.getFileItemShowIcon(this.resolveFileSuffix(item))
},
resolveFileSuffix(item) {
const fileType = String(item.fileType || "").replace(".", "").toLowerCase()
if (this.isKnownSuffix(fileType)) return fileType
const fileNameSuffix = this.extractSuffix(item.fileName || item.name || "")
if (fileNameSuffix) return fileNameSuffix
const fileUrlSuffix = this.extractSuffix((item.fileUrl || "").split("?")[0])
if (fileUrlSuffix) return fileUrlSuffix
if (fileType.indexOf("pdf") > -1) return "pdf"
if (fileType.indexOf("word") > -1) return "docx"
if (fileType.indexOf("excel") > -1 || fileType.indexOf("sheet") > -1) return "xlsx"
if (fileType.indexOf("powerpoint") > -1 || fileType.indexOf("presentation") > -1) return "pptx"
if (fileType.indexOf("image") > -1) return "jpg"
if (fileType.indexOf("zip") > -1 || fileType.indexOf("rar") > -1) return "zip"
if (fileType.indexOf("text") > -1) return "txt"
if (fileType.indexOf("video") > -1) return "mp4"
if (fileType.indexOf("audio") > -1) return "mp3"
return ""
},
extractSuffix(value) {
const text = String(value || "").trim()
const index = text.lastIndexOf(".")
if (index < 0 || index === text.length - 1) return ""
const suffix = text.substring(index + 1).toLowerCase()
return this.isKnownSuffix(suffix) ? suffix : ""
},
isKnownSuffix(suffix) {
return ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "zip", "rar", "jpg", "jpeg", "png", "gif", "bmp", "webp", "mp4", "avi", "wmv", "rmvb", "flv", "mkv", "mp3", "wav", "wma", "flac", "ape"].indexOf(suffix) > -1
},
formatTime(time) {
if (!time) return ""
@@ -126,12 +155,26 @@ layout("/layouts/platform_h5.html"){
return
}
this.$pjaxReplace("/platform/h5/congress/pdf?pdfPath=" + encodeURIComponent(item.fileUrl) +
"&fileName=" + encodeURIComponent(item.fileName || item.name || ""))
"&fileName=" + encodeURIComponent(item.fileName || item.name || "") +
"&fileType=" + encodeURIComponent(item.fileType || "") +
"&sysFileId=" + encodeURIComponent(this.resolveSysFileId(item.fileUrl)))
},
openVotePdf(item) {
this.$pjaxReplace("/platform/h5/congress/pdf?pdfPath=" + encodeURIComponent(item.fileUrl || "") +
"&fileName=" + encodeURIComponent(item.fileName || item.name || "") +
"&fileId=" + encodeURIComponent(item.id || ""))
"&materialsId=" + encodeURIComponent(item.id || "") +
"&fileType=" + encodeURIComponent(item.fileType || "") +
"&sysFileId=" + encodeURIComponent(this.resolveSysFileId(item.fileUrl)))
},
resolveSysFileId(fileUrl) {
if (!fileUrl) return ""
try {
const url = new URL(fileUrl, window.location.origin)
return url.searchParams.get("id") || ""
} catch (e) {
const match = String(fileUrl).match(/[?&]id=([^&]+)/)
return match ? decodeURIComponent(match[1]) : ""
}
},
isVoted(value) {
return value === true || value === 1 || value === "1" || value === "true"
@@ -224,12 +267,19 @@ layout("/layouts/platform_h5.html"){
.icon-wrapper {
width: 52px;
height: 52px;
border-radius: 14px;
background: linear-gradient(135deg, #f0f5ff 0%, #e6f0ff 100%);
border-radius: 12px;
background: #f7f8fa;
display: flex;
align-items: center;
justify-content: center;
color: #1989fa;
border: 1px solid #eef0f4;
}
.file-type-icon {
width: 38px;
height: 38px;
display: block;
object-fit: contain;
}
.card-content {
@@ -3,7 +3,7 @@ layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar title="会议签到" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-nav-bar title="会议签到" left-text="返回" left-arrow @click-left="backToCongress" placeholder fixed></van-nav-bar>
<div class="meeting-list-page">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
@@ -63,13 +63,19 @@ layout("/layouts/platform_h5.html"){
finished: false,
refreshing: false,
loadedOnce: false,
timeId: ""
timeId: "",
sessionId: ""
}
},
methods: {
initQuery() {
const params = new URLSearchParams(window.location.search)
this.timeId = params.get("timeId") || ""
this.sessionId = params.get("sessionId") || this.timeId
},
backToCongress() {
const timeId = this.timeId || this.sessionId || window.localStorage.getItem("congressTimeId") || ""
this.$pjaxReplace("/platform/h5/congress" + (timeId ? "?timeId=" + encodeURIComponent(timeId) : ""))
},
loginName() {
const user = (this.$store.state && this.$store.state.user) || JSON.parse(window.sessionStorage.getItem("user") || "{}")
@@ -4,11 +4,15 @@ layout("/layouts/platform_h5.html"){
<div id="app" v-cloak>
<div class="pdf-viewer-page">
<van-nav-bar :title="fileName || 'PDF查看'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<van-nav-bar :title="fileName || '文件预览'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="pdf-container" :class="{'has-footer': voteInfo}">
<iframe v-if="pdfPath" :src="previewUrl" class="pdf-frame"></iframe>
<div v-else class="pdf-empty"></div>
<div v-if="previewMode === 'pdf'" id="congress-pdf-container" class="pdf-render-box"></div>
<img v-else-if="previewMode === 'image'" :src="previewUrl" class="image-preview" @click="previewImage">
<iframe v-else-if="previewMode === 'iframe'" :src="previewUrl" class="pdf-frame"></iframe>
<div v-else class="pdf-empty">
<van-empty description="暂无可预览附件"></van-empty>
</div>
</div>
<div v-if="voteInfo" class="footer-bar">
@@ -25,13 +29,22 @@ layout("/layouts/platform_h5.html"){
pdfPath: "",
fileName: "",
fileId: "",
materialsId: "",
sysFileId: "",
fileType: "",
previewUrl: "",
previewMode: "empty",
pdfObj: null,
voteInfo: null
}
},
computed: {
previewUrl() {
if (!this.pdfPath) return ""
return this.pdfPath
fileSuffix() {
const name = this.fileName || ""
const url = (this.pdfPath || "").split("?")[0]
const source = name.indexOf(".") > -1 ? name : url
const index = source.lastIndexOf(".")
return index > -1 ? source.substring(index + 1).toLowerCase() : ""
}
},
methods: {
@@ -40,12 +53,101 @@ layout("/layouts/platform_h5.html"){
this.pdfPath = params.get("pdfPath") || ""
this.fileName = params.get("fileName") || ""
this.fileId = params.get("fileId") || ""
this.materialsId = params.get("materialsId") || params.get("materialId") || this.fileId
this.sysFileId = params.get("sysFileId") || ""
this.fileType = params.get("fileType") || ""
},
initPreview() {
if (!this.pdfPath) {
this.previewMode = "empty"
return
}
const suffix = this.resolveSuffix()
if (this.isImage(suffix)) {
this.previewUrl = this.pdfPath
this.previewMode = "image"
return
}
if (suffix === "pdf" || !suffix) {
this.previewUrl = this.pdfPath
this.previewMode = "pdf"
this.renderPdf(this.previewUrl)
return
}
if (this.isOfficeFile(suffix)) {
const fileId = this.resolveFileId()
if (fileId) {
this.previewUrl = "/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent("/platform/sys/file/convertPDF?id=" + fileId)
this.previewMode = "iframe"
return
}
}
this.previewUrl = this.pdfPath
this.previewMode = "iframe"
},
resolveSuffix() {
if (this.fileSuffix) return this.fileSuffix
const type = String(this.fileType || "").replace(".", "").toLowerCase()
if (["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "gif", "bmp", "webp"].indexOf(type) > -1) {
return type
}
if (type.indexOf("pdf") > -1) return "pdf"
if (type.indexOf("word") > -1) return "docx"
if (type.indexOf("excel") > -1 || type.indexOf("sheet") > -1) return "xlsx"
if (type.indexOf("powerpoint") > -1 || type.indexOf("presentation") > -1) return "pptx"
if (type.indexOf("image") > -1) return "jpg"
return ""
},
resolveFileId() {
if (this.sysFileId) return this.sysFileId
if (!this.pdfPath) return ""
try {
const url = new URL(this.pdfPath, window.location.origin)
return url.searchParams.get("id") || ""
} catch (e) {
const match = this.pdfPath.match(/[?&]id=([^&]+)/)
return match ? decodeURIComponent(match[1]) : ""
}
},
isImage(suffix) {
return ["jpg", "jpeg", "png", "gif", "bmp", "webp"].indexOf(suffix) > -1
},
isOfficeFile(suffix) {
return ["doc", "docx", "xls", "xlsx", "ppt", "pptx"].indexOf(suffix) > -1
},
renderPdf(url) {
this.$nextTick(() => {
const container = document.getElementById("congress-pdf-container")
if (!container || !url) return
container.innerHTML = ""
try {
if (this.pdfObj && this.pdfObj.destroy) {
this.pdfObj.destroy()
}
} catch (e) {}
try {
this.pdfObj = new Pdfh5("#congress-pdf-container", {
pdfurl: url,
lazy: true
})
} catch (e) {
this.previewUrl = "/assets/platform/plugins/pdfJs/web/viewer.html?file=" + encodeURIComponent(url)
this.previewMode = "iframe"
}
})
},
previewImage() {
if (!this.previewUrl) return
vant.ImagePreview({
images: [this.previewUrl],
closeable: true
})
},
fetchVoteInfo() {
if (!this.fileId) return
if (!this.materialsId) return
const repId = window.localStorage.getItem("repId") || ""
this.$axios.get("/platform/h5/congress/votingIssue", {
params: {materialsId: this.fileId, repId: repId}
params: {materialsId: this.materialsId, repId: repId}
}).then((res) => {
if (res.code === 0 && res.data) {
this.voteInfo = res.data
@@ -67,6 +169,7 @@ layout("/layouts/platform_h5.html"){
created() {
this.initQuery()
this.fetchVoteInfo()
this.initPreview()
}
})
</script>
@@ -77,22 +180,11 @@ layout("/layouts/platform_h5.html"){
background: #f5f6f8;
}
.pdf-viewer-page .van-nav-bar {
background: #1989fa;
box-shadow: 0 4px 16px rgba(25, 137, 250, 0.15);
}
.pdf-viewer-page .van-nav-bar__title,
.pdf-viewer-page .van-nav-bar .van-icon,
.pdf-viewer-page .van-nav-bar__text {
color: #fff;
font-weight: 600;
}
.pdf-container {
height: calc(100vh - 46px);
background: #f5f6f8;
overflow: hidden;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.pdf-container.has-footer {
@@ -107,10 +199,32 @@ layout("/layouts/platform_h5.html"){
background: #fff;
}
.pdf-render-box {
width: 100%;
min-height: 100%;
background: #f5f6f8;
}
.pdf-render-box .pdfjs {
background: #f5f6f8;
}
.image-preview {
display: block;
width: 100%;
height: auto;
min-height: 100%;
object-fit: contain;
background: #fff;
}
.pdf-empty {
width: 100%;
height: 100%;
background: #f5f6f8;
display: flex;
align-items: center;
justify-content: center;
}
.footer-bar {
@@ -3,6 +3,8 @@ layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar :title="voteInfo.name || '投票'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="vote-page">
<div class="vote-content">
<div class="vote-header-card">
@@ -14,10 +16,10 @@ layout("/layouts/platform_h5.html"){
<span>{{ sessionName }}</span>
</div>
<div class="meta-divider"></div>
<div class="meta-item">
<van-icon name="clock-o" size="14"></van-icon>
<span>{{ timeName }}</span>
</div>
<!-- <div class="meta-item">-->
<!-- <van-icon name="clock-o" size="14"></van-icon>-->
<!-- <span>{{ timeName }}</span>-->
<!-- </div>-->
</div>
</div>
@@ -199,7 +201,7 @@ layout("/layouts/platform_h5.html"){
<style id="style-congress-vote-h5">
.vote-page {
min-height: 100vh;
min-height: calc(100vh - 46px);
background: #f5f7fa;
display: flex;
flex-direction: column;
@@ -3,6 +3,8 @@ layout("/layouts/platform_h5.html"){
#-->
<div id="app" v-cloak>
<van-nav-bar :title="voteInfo.name || '查看投票'" left-text="返回" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
<div class="vote-page">
<div class="vote-content">
<div class="vote-header-card">
@@ -14,10 +16,10 @@ layout("/layouts/platform_h5.html"){
<span>{{ sessionName }}</span>
</div>
<div class="meta-divider"></div>
<div class="meta-item">
<van-icon name="clock-o" size="14"></van-icon>
<span>{{ timeName }}</span>
</div>
<!-- <div class="meta-item">-->
<!-- <van-icon name="clock-o" size="14"></van-icon>-->
<!-- <span>{{ timeName }}</span>-->
<!-- </div>-->
</div>
</div>
@@ -183,7 +185,7 @@ layout("/layouts/platform_h5.html"){
<style id="style-congress-voted-h5">
.vote-page {
min-height: 100vh;
min-height: calc(100vh - 46px);
background: #f5f7fa;
display: flex;
flex-direction: column;