fix bugs
This commit is contained in:
@@ -19,7 +19,20 @@
|
||||
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES;\")",
|
||||
"Bash(\"D:\\Program Files\\mysql-8.0.32-winx64\\bin\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE athlete;\")",
|
||||
"Bash(tree:*)",
|
||||
"Bash(find:*)"
|
||||
"Bash(find:*)",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW TABLES LIKE ''%attachment%'';\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE martial_competition_rules_attachment;\")",
|
||||
"Bash(ls -la \"d:\\\\workspace\\\\31.比赛项目\\\\project\\\\martial-mini\\\\src\\\\pages\\\\attachment-view\"\" 2>/dev/null && ls -la \"d:workspace31.比赛项目projectmartial-minisrcpagesevent-photos\"\")",
|
||||
"Bash(npm install:*)",
|
||||
"Bash(npm uninstall:*)",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT id, name FROM martial_competition LIMIT 5;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE martial_competition;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT id, competition_name, total_participants FROM martial_competition WHERE is_deleted = 0 LIMIT 5;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESCRIBE martial_registration_order;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\":*)",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT id, competition_name, total_participants FROM martial_competition WHERE id = 200;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT competition_id, COUNT\\(*\\) as order_count, SUM\\(total_participants\\) as total_people, is_deleted FROM martial_registration_order WHERE competition_id = 200 GROUP BY competition_id, is_deleted;\")",
|
||||
"Bash(\"D:\\\\Program Files\\\\mysql-8.0.32-winx64\\\\bin\\\\mysql.exe\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT id, competition_name, total_participants, is_deleted FROM martial_competition WHERE id = 200;\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -61,5 +61,28 @@ export default {
|
||||
*/
|
||||
getCompetitionRules(competitionId) {
|
||||
return request.get('/martial/competition/rules', { competitionId })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取赛事附件列表
|
||||
* @param {Object} params { competitionId, type }
|
||||
* type: info-信息发布, rules-赛事规程, schedule-活动日程,
|
||||
* results-成绩, medals-奖牌榜, photos-图片直播
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAttachments(params = {}) {
|
||||
return request.get('/martial/competition/attachment/getByType', {
|
||||
competitionId: params.competitionId,
|
||||
attachmentType: params.type
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取赛事所有附件
|
||||
* @param {String|Number} competitionId 赛事ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAllAttachments(competitionId) {
|
||||
return request.get('/martial/competition/attachment/getByCompetition', { competitionId })
|
||||
}
|
||||
}
|
||||
|
||||
45
package-lock.json
generated
45
package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"@dcloudio/uni-migration": "^2.0.2-4080720251210002",
|
||||
"cache-loader": "^4.1.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"pdfjs-dist": "^2.16.105",
|
||||
"vue": "^2.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -76,6 +77,7 @@
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -2854,6 +2856,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-4.5.19.tgz",
|
||||
"integrity": "sha512-+Wpvj8fMTCt9ZPOLu5YaLkFCQmB4MrZ26aRmhhKiCQ/4PMoL6mLezfqdt6c+m2htM+1WV5RunRo+0WHl2DfwZA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@intervolga/optimize-cssnano-plugin": "^1.0.5",
|
||||
"@soda/friendly-errors-webpack-plugin": "^1.7.1",
|
||||
@@ -3514,6 +3517,7 @@
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -4410,6 +4414,7 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -5329,6 +5334,7 @@
|
||||
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz",
|
||||
"integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cacache": "^12.0.3",
|
||||
"find-cache-dir": "^2.1.0",
|
||||
@@ -6425,6 +6431,13 @@
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dommatrix": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz",
|
||||
"integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==",
|
||||
"deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||
@@ -7369,6 +7382,7 @@
|
||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz",
|
||||
"integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loader-utils": "^1.2.3",
|
||||
"schema-utils": "^2.5.0"
|
||||
@@ -8440,6 +8454,7 @@
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz",
|
||||
"integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/html-minifier-terser": "^5.0.0",
|
||||
"@types/tapable": "^1.0.5",
|
||||
@@ -11295,6 +11310,24 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/pdfjs-dist": {
|
||||
"version": "2.16.105",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz",
|
||||
"integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dommatrix": "^1.0.3",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"worker-loader": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -11403,6 +11436,7 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
|
||||
"integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"picocolors": "^0.2.1",
|
||||
"source-map": "^0.6.1"
|
||||
@@ -13029,6 +13063,7 @@
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.96.0.tgz",
|
||||
"integrity": "sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -16032,10 +16067,20 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "4.47.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz",
|
||||
"integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.9.0",
|
||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@dcloudio/uni-migration": "^2.0.2-4080720251210002",
|
||||
"cache-loader": "^4.1.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"pdfjs-dist": "^2.16.105",
|
||||
"vue": "^2.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
880
pages/attachment-view/attachment-view.vue
Normal file
880
pages/attachment-view/attachment-view.vue
Normal file
@@ -0,0 +1,880 @@
|
||||
<template>
|
||||
<view class="attachment-page">
|
||||
<!-- 赛事信息卡片 -->
|
||||
<view class="event-info-card">
|
||||
<view class="event-title">{{ competitionName || '赛事名称' }}</view>
|
||||
<view class="event-time-row">
|
||||
<view class="time-item">
|
||||
<text class="time-label">开始时间</text>
|
||||
<text class="time-value">{{ startTime || '待定' }}</text>
|
||||
</view>
|
||||
<view class="time-divider"></view>
|
||||
<view class="time-item">
|
||||
<text class="time-label">结束时间</text>
|
||||
<text class="time-value">{{ endTime || '待定' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 附件列表 -->
|
||||
<view class="attachments-section" v-if="!loading && attachments.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="section-title">{{ pageTitle }}文件</text>
|
||||
<text class="section-count">共{{ attachments.length }}个文件</text>
|
||||
</view>
|
||||
|
||||
<view class="attachments-list">
|
||||
<view
|
||||
class="attachment-item"
|
||||
v-for="(file, index) in attachments"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 文件图标 -->
|
||||
<view class="file-icon" :class="'icon-' + file.fileType">
|
||||
<text class="icon-text">{{ getFileIconText(file.fileType) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 文件信息 -->
|
||||
<view class="file-content">
|
||||
<text class="file-name">{{ file.fileName }}</text>
|
||||
<view class="file-meta">
|
||||
<text class="meta-item">{{ file.fileSize }}</text>
|
||||
<text class="meta-dot" v-if="file.uploadTime">·</text>
|
||||
<text class="meta-item" v-if="file.uploadTime">{{ file.uploadTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="file-actions">
|
||||
<view class="action-btn preview-btn" @click="previewFile(file)">
|
||||
<text class="action-text">预览</text>
|
||||
</view>
|
||||
<view class="action-btn download-btn" @click="downloadFile(file)">
|
||||
<text class="action-text">下载</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && attachments.length === 0">
|
||||
<image class="empty-image" src="/static/images/empty.png" mode="aspectFit" />
|
||||
<text class="empty-title">暂无{{ pageTitle }}文件</text>
|
||||
<text class="empty-desc">相关文件正在整理中,请稍后查看</text>
|
||||
</view>
|
||||
|
||||
<!-- PDF预览弹窗 (仅H5) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="preview-modal" v-if="showPreview" @click="closePreview">
|
||||
<view class="preview-container" @click.stop>
|
||||
<view class="preview-header">
|
||||
<text class="preview-title">{{ previewFileName }}</text>
|
||||
<view class="preview-close" @click="closePreview">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="preview-body">
|
||||
<iframe
|
||||
:src="previewUrl"
|
||||
class="preview-iframe"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import competitionAPI from '@/api/competition.js'
|
||||
|
||||
// 页面类型配置
|
||||
const PAGE_CONFIG = {
|
||||
'info': { title: '信息发布', type: 'info' },
|
||||
'rules': { title: '赛事规程', type: 'rules' },
|
||||
'schedule': { title: '活动日程', type: 'schedule' },
|
||||
'score': { title: '成绩公告', type: 'results' },
|
||||
'results': { title: '成绩公告', type: 'results' },
|
||||
'awards': { title: '奖牌榜', type: 'medals' },
|
||||
'medals': { title: '奖牌榜', type: 'medals' },
|
||||
'photos': { title: '图片直播', type: 'photos' }
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
pageType: 'rules',
|
||||
pageTitle: '赛事规程',
|
||||
competitionId: '',
|
||||
competitionName: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
attachments: [],
|
||||
// H5预览相关
|
||||
showPreview: false,
|
||||
previewUrl: '',
|
||||
previewFileName: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取页面类型
|
||||
if (options.type && PAGE_CONFIG[options.type]) {
|
||||
this.pageType = options.type
|
||||
this.pageTitle = PAGE_CONFIG[options.type].title
|
||||
}
|
||||
|
||||
// 获取赛事ID
|
||||
if (options.competitionId || options.eventId) {
|
||||
this.competitionId = options.competitionId || options.eventId
|
||||
}
|
||||
|
||||
// 获取赛事名称
|
||||
if (options.name) {
|
||||
this.competitionName = decodeURIComponent(options.name)
|
||||
}
|
||||
|
||||
// 获取比赛时间
|
||||
if (options.startTime) {
|
||||
this.startTime = decodeURIComponent(options.startTime)
|
||||
}
|
||||
if (options.endTime) {
|
||||
this.endTime = decodeURIComponent(options.endTime)
|
||||
}
|
||||
|
||||
// 设置导航栏标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: this.pageTitle
|
||||
})
|
||||
|
||||
// 加载数据
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 如果没有赛事信息,先获取赛事详情
|
||||
if (!this.startTime || !this.endTime) {
|
||||
await this.loadCompetitionInfo()
|
||||
}
|
||||
// 加载附件列表
|
||||
await this.loadAttachments()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载赛事信息
|
||||
*/
|
||||
async loadCompetitionInfo() {
|
||||
try {
|
||||
const res = await competitionAPI.getCompetitionDetail(this.competitionId)
|
||||
if (res) {
|
||||
this.competitionName = res.name || res.title || this.competitionName
|
||||
this.startTime = this.formatDate(res.startTime || res.competitionStartTime)
|
||||
this.endTime = this.formatDate(res.endTime || res.competitionEndTime)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载赛事信息失败:', err)
|
||||
// 使用模拟数据
|
||||
this.startTime = '2025.12.12'
|
||||
this.endTime = '2025.12.14'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载附件列表
|
||||
*/
|
||||
async loadAttachments() {
|
||||
try {
|
||||
// 使用 PAGE_CONFIG 映射的 type 值
|
||||
const attachmentType = PAGE_CONFIG[this.pageType]?.type || this.pageType
|
||||
console.log('=== 加载附件 ===')
|
||||
console.log('competitionId:', this.competitionId)
|
||||
console.log('pageType:', this.pageType)
|
||||
console.log('attachmentType (发送给后端):', attachmentType)
|
||||
|
||||
const res = await competitionAPI.getAttachments({
|
||||
competitionId: this.competitionId,
|
||||
type: attachmentType
|
||||
})
|
||||
|
||||
console.log('API返回结果:', res)
|
||||
console.log('API返回结果类型:', typeof res)
|
||||
console.log('是否为数组:', Array.isArray(res))
|
||||
|
||||
// 兼容不同的返回格式
|
||||
let attachmentList = []
|
||||
if (Array.isArray(res)) {
|
||||
attachmentList = res
|
||||
} else if (res && res.records && Array.isArray(res.records)) {
|
||||
// 分页格式
|
||||
attachmentList = res.records
|
||||
} else if (res && typeof res === 'object') {
|
||||
// 可能是单个对象,转为数组
|
||||
attachmentList = [res]
|
||||
}
|
||||
|
||||
console.log('处理后的附件列表:', attachmentList)
|
||||
|
||||
if (attachmentList.length > 0) {
|
||||
this.attachments = attachmentList.map(file => ({
|
||||
id: file.id,
|
||||
fileName: file.fileName || file.name,
|
||||
fileUrl: file.fileUrl || file.url,
|
||||
fileSize: this.formatFileSize(file.fileSize || file.size),
|
||||
fileType: this.getFileType(file.fileName || file.name),
|
||||
uploadTime: this.formatDate(file.uploadTime || file.createTime)
|
||||
}))
|
||||
console.log('附件加载成功,共', this.attachments.length, '个文件')
|
||||
} else {
|
||||
console.log('没有附件数据,显示空状态')
|
||||
this.attachments = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('=== 加载附件失败 ===')
|
||||
console.error('错误详情:', err)
|
||||
console.error('错误消息:', err.message)
|
||||
console.error('错误代码:', err.code)
|
||||
// 显示空状态,不使用模拟数据,方便调试
|
||||
this.attachments = []
|
||||
// 如果需要模拟数据,取消下面的注释
|
||||
// this.loadMockData()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载模拟数据
|
||||
*/
|
||||
loadMockData() {
|
||||
const mockDataMap = {
|
||||
'info': [
|
||||
{ id: '1', fileName: '2025年郑州武术大赛通知.pdf', fileUrl: '', fileSize: '1.2 MB', fileType: 'pdf', uploadTime: '2025-12-20' }
|
||||
],
|
||||
'rules': [
|
||||
{ id: '1', fileName: '2025年郑州武术大赛竞赛规程.pdf', fileUrl: '', fileSize: '2.5 MB', fileType: 'pdf', uploadTime: '2025-12-18' },
|
||||
{ id: '2', fileName: '参赛报名表.pdf', fileUrl: '', fileSize: '156 KB', fileType: 'pdf', uploadTime: '2025-12-18' }
|
||||
],
|
||||
'schedule': [
|
||||
{ id: '1', fileName: '比赛日程安排表.pdf', fileUrl: '', fileSize: '890 KB', fileType: 'pdf', uploadTime: '2025-12-19' }
|
||||
],
|
||||
'score': [
|
||||
{ id: '1', fileName: '比赛成绩公告.pdf', fileUrl: '', fileSize: '1.8 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'results': [
|
||||
{ id: '1', fileName: '比赛成绩公告.pdf', fileUrl: '', fileSize: '1.8 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'awards': [
|
||||
{ id: '1', fileName: '奖牌榜统计.pdf', fileUrl: '', fileSize: '520 KB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'medals': [
|
||||
{ id: '1', fileName: '奖牌榜统计.pdf', fileUrl: '', fileSize: '520 KB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'photos': [
|
||||
{ id: '1', fileName: '比赛精彩瞬间.pdf', fileUrl: '', fileSize: '15.6 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
]
|
||||
}
|
||||
|
||||
this.attachments = mockDataMap[this.pageType] || []
|
||||
},
|
||||
|
||||
/**
|
||||
* 预览文件
|
||||
*/
|
||||
previewFile(file) {
|
||||
if (!file.fileUrl) {
|
||||
uni.showToast({
|
||||
title: '文件暂不可用',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5端:根据文件类型选择预览方式
|
||||
if (file.fileType === 'pdf') {
|
||||
// 方案1: 直接在新标签页打开PDF(浏览器内置PDF阅读器)
|
||||
window.open(file.fileUrl, '_blank')
|
||||
|
||||
// 方案2: 使用微软 Office Online Viewer(备选,需要公网可访问)
|
||||
// const msViewerUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(file.fileUrl)}`
|
||||
// window.open(msViewerUrl, '_blank')
|
||||
|
||||
// 方案3: 使用内嵌弹窗(如果服务器支持)
|
||||
// this.previewFileName = file.fileName
|
||||
// this.previewUrl = file.fileUrl
|
||||
// this.showPreview = true
|
||||
} else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.fileType)) {
|
||||
// 图片使用弹窗显示
|
||||
this.previewFileName = file.fileName
|
||||
this.previewUrl = file.fileUrl
|
||||
this.showPreview = true
|
||||
} else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(file.fileType)) {
|
||||
// Office 文档使用微软在线预览
|
||||
const msViewerUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(file.fileUrl)}`
|
||||
window.open(msViewerUrl, '_blank')
|
||||
} else {
|
||||
// 其他文件类型,在新窗口打开
|
||||
window.open(file.fileUrl, '_blank')
|
||||
}
|
||||
return
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 非H5端使用下载+打开文档的方式
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
})
|
||||
|
||||
uni.downloadFile({
|
||||
url: file.fileUrl,
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
if (res.statusCode === 200) {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true,
|
||||
success: () => {
|
||||
console.log('打开文档成功')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开文档失败:', err)
|
||||
uni.showToast({
|
||||
title: '无法预览此文件',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '文件加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error('下载失败:', err)
|
||||
uni.showToast({
|
||||
title: '下载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭预览弹窗
|
||||
*/
|
||||
closePreview() {
|
||||
this.showPreview = false
|
||||
this.previewUrl = ''
|
||||
this.previewFileName = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
*/
|
||||
downloadFile(file) {
|
||||
if (!file.fileUrl) {
|
||||
uni.showToast({
|
||||
title: '文件暂不可用',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '下载中...'
|
||||
})
|
||||
|
||||
uni.downloadFile({
|
||||
url: file.fileUrl,
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
if (res.statusCode === 200) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序保存文件
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
// 保存失败则打开文档
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5端打开新窗口下载
|
||||
window.open(file.fileUrl)
|
||||
uni.showToast({
|
||||
title: '开始下载',
|
||||
icon: 'success'
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// APP端保存到相册或文件
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '下载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error('下载失败:', err)
|
||||
uni.showToast({
|
||||
title: '下载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件类型
|
||||
*/
|
||||
getFileType(fileName) {
|
||||
if (!fileName) return 'pdf'
|
||||
const ext = fileName.split('.').pop().toLowerCase()
|
||||
return ext
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件图标文字
|
||||
*/
|
||||
getFileIconText(fileType) {
|
||||
const iconMap = {
|
||||
'pdf': 'PDF',
|
||||
'doc': 'DOC',
|
||||
'docx': 'DOC',
|
||||
'xls': 'XLS',
|
||||
'xlsx': 'XLS',
|
||||
'ppt': 'PPT',
|
||||
'pptx': 'PPT',
|
||||
'txt': 'TXT',
|
||||
'jpg': 'IMG',
|
||||
'jpeg': 'IMG',
|
||||
'png': 'IMG',
|
||||
'zip': 'ZIP',
|
||||
'rar': 'RAR'
|
||||
}
|
||||
return iconMap[fileType] || 'FILE'
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
formatFileSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
if (typeof bytes === 'string') return bytes
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i]
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return dateStr
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.attachment-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
// 赛事信息卡片
|
||||
.event-info-card {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.event-time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.time-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top-color: #C93639;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 附件区域
|
||||
.attachments-section {
|
||||
margin: 20rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.attachments-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
// 附件项
|
||||
.attachment-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
// 文件图标
|
||||
.file-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
background: #C93639;
|
||||
}
|
||||
|
||||
.file-icon.icon-pdf {
|
||||
background: #E74C3C;
|
||||
}
|
||||
|
||||
.file-icon.icon-doc,
|
||||
.file-icon.icon-docx {
|
||||
background: #3498DB;
|
||||
}
|
||||
|
||||
.file-icon.icon-xls,
|
||||
.file-icon.icon-xlsx {
|
||||
background: #27AE60;
|
||||
}
|
||||
|
||||
.file-icon.icon-ppt,
|
||||
.file-icon.icon-pptx {
|
||||
background: #E67E22;
|
||||
}
|
||||
|
||||
.file-icon.icon-jpg,
|
||||
.file-icon.icon-jpeg,
|
||||
.file-icon.icon-png {
|
||||
background: #9B59B6;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// 文件内容
|
||||
.file-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.meta-dot {
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
.file-actions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
background: #C93639;
|
||||
|
||||
.action-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: #fff;
|
||||
border: 1rpx solid #C93639;
|
||||
|
||||
.action-text {
|
||||
color: #C93639;
|
||||
}
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 60rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// PDF预览弹窗样式
|
||||
.preview-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
width: 95%;
|
||||
height: 90%;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #C93639;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.preview-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.preview-body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
res.registerTime || res.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
res.matchTime || res.competitionTime || '待定',
|
||||
registerCount: res.registrationCount || res.registerCount || res.signUpCount || '0',
|
||||
registerCount: res.registrationCount || res.registerCount || res.signUpCount || res.totalParticipants || '0',
|
||||
status: this.getStatus(res.status)
|
||||
}
|
||||
|
||||
@@ -169,16 +169,23 @@ export default {
|
||||
},
|
||||
|
||||
handleFunction(type) {
|
||||
// 需要跳转到附件展示页面的类型
|
||||
const attachmentTypes = ['info', 'rules', 'schedule', 'score', 'awards', 'photos']
|
||||
|
||||
if (attachmentTypes.includes(type)) {
|
||||
// 跳转到通用附件展示页面
|
||||
const name = encodeURIComponent(this.eventInfo.title)
|
||||
uni.navigateTo({
|
||||
url: `/pages/attachment-view/attachment-view?type=${type}&competitionId=${this.eventId}&name=${name}`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 其他功能页面的路由映射
|
||||
const routeMap = {
|
||||
'info': '/pages/event-info/event-info',
|
||||
'rules': '/pages/event-rules/event-rules',
|
||||
'schedule': '/pages/event-schedule/event-schedule',
|
||||
'players': '/pages/event-players/event-players',
|
||||
'match': '/pages/event-live/event-live',
|
||||
'lineup': '/pages/event-lineup/event-lineup',
|
||||
'score': '/pages/event-score/event-score',
|
||||
'awards': '/pages/event-medals/event-medals',
|
||||
'photos': '' // 图片直播暂未实现
|
||||
'lineup': '/pages/event-lineup/event-lineup'
|
||||
};
|
||||
|
||||
const url = routeMap[type];
|
||||
|
||||
@@ -228,7 +228,7 @@ export default {
|
||||
item.registerTime || item.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
item.matchTime || item.competitionTime || '待定',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || '0',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || item.totalParticipants || '0',
|
||||
status: this.getStatus(item.status)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
item.registerTime || item.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
item.matchTime || item.competitionTime || '待定',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || '0',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || item.totalParticipants || '0',
|
||||
status: this.getStatus(item.status)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -243,7 +243,7 @@ export default {
|
||||
matchTime: '',
|
||||
projects: '',
|
||||
contact: orderItem.contactPhone || '',
|
||||
participants: `${orderItem.totalParticipants || 0}人`
|
||||
participants: `${orderItem.registerCount || 0}人`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="label">参赛人数:</text>
|
||||
<text class="value">{{ scheduleData.totalParticipants || 0 }}</text>
|
||||
<text class="value">{{ scheduleData.registerCount || 0 }}</text>
|
||||
</view>
|
||||
<view class="status-item" v-if="scheduleData.lastAutoScheduleTime">
|
||||
<text class="label">最后编排时间:</text>
|
||||
|
||||
@@ -61,5 +61,28 @@ export default {
|
||||
*/
|
||||
getCompetitionRules(competitionId) {
|
||||
return request.get('/martial/competition/rules', { competitionId })
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取赛事附件列表
|
||||
* @param {Object} params { competitionId, type }
|
||||
* type: info-信息发布, rules-赛事规程, schedule-活动日程,
|
||||
* results-成绩, medals-奖牌榜, photos-图片直播
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAttachments(params = {}) {
|
||||
return request.get('/martial/competition/attachment/getByType', {
|
||||
competitionId: params.competitionId,
|
||||
attachmentType: params.type
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取赛事所有附件
|
||||
* @param {String|Number} competitionId 赛事ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAllAttachments(competitionId) {
|
||||
return request.get('/martial/competition/attachment/getByCompetition', { competitionId })
|
||||
}
|
||||
}
|
||||
|
||||
508
src/components/pdf-viewer/pdf-viewer.vue
Normal file
508
src/components/pdf-viewer/pdf-viewer.vue
Normal file
@@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<view class="pdf-viewer-modal" v-if="visible" @click="handleClose">
|
||||
<view class="pdf-container" @click.stop>
|
||||
<!-- 头部 -->
|
||||
<view class="pdf-header">
|
||||
<text class="pdf-title">{{ fileName }}</text>
|
||||
<view class="pdf-actions">
|
||||
<text class="page-info">{{ currentPage }} / {{ totalPages }}</text>
|
||||
<view class="close-btn" @click="handleClose">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- PDF 内容区域 -->
|
||||
<view class="pdf-content" ref="pdfContent">
|
||||
<!-- 加载中 -->
|
||||
<view class="loading-wrapper" v-if="loading">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中... {{ loadingProgress }}%</text>
|
||||
</view>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view class="error-wrapper" v-if="error">
|
||||
<text class="error-text">{{ error }}</text>
|
||||
<view class="retry-btn" @click="loadPdf">
|
||||
<text>重试</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- PDF 画布容器 - 使用原生 div -->
|
||||
<view
|
||||
class="pdf-scroll-wrapper"
|
||||
v-show="!loading && !error"
|
||||
ref="scrollWrapper"
|
||||
>
|
||||
<view class="canvas-container" ref="canvasContainer">
|
||||
<!-- canvas 将通过 JS 动态创建 -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部工具栏 -->
|
||||
<view class="pdf-toolbar">
|
||||
<view class="toolbar-btn" :class="{ disabled: currentPage <= 1 }" @click="prevPage">
|
||||
<text>上一页</text>
|
||||
</view>
|
||||
<view class="toolbar-btn" :class="{ disabled: currentPage >= totalPages }" @click="nextPage">
|
||||
<text>下一页</text>
|
||||
</view>
|
||||
<view class="toolbar-btn" @click="zoomOut">
|
||||
<text>缩小</text>
|
||||
</view>
|
||||
<view class="toolbar-btn" @click="zoomIn">
|
||||
<text>放大</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef H5
|
||||
let pdfjsLib = null
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
name: 'PdfViewer',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: 'PDF文档'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadingProgress: 0,
|
||||
error: '',
|
||||
pdfDoc: null,
|
||||
currentPage: 1,
|
||||
totalPages: 0,
|
||||
scale: 1.2,
|
||||
canvasElements: {},
|
||||
pdfjsLoaded: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val && this.url) {
|
||||
this.$nextTick(() => {
|
||||
this.initPdfjs()
|
||||
})
|
||||
} else if (!val) {
|
||||
this.cleanup()
|
||||
}
|
||||
},
|
||||
url(val) {
|
||||
if (this.visible && val) {
|
||||
this.$nextTick(() => {
|
||||
this.initPdfjs()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initPdfjs() {
|
||||
// #ifdef H5
|
||||
if (!pdfjsLib) {
|
||||
try {
|
||||
// 动态导入 pdfjs-dist
|
||||
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.js')
|
||||
pdfjsLib = pdfjs
|
||||
|
||||
// 设置 worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js'
|
||||
|
||||
this.pdfjsLoaded = true
|
||||
console.log('PDF.js 加载成功')
|
||||
} catch (err) {
|
||||
console.error('加载 PDF.js 失败:', err)
|
||||
this.error = '加载 PDF 组件失败'
|
||||
return
|
||||
}
|
||||
}
|
||||
this.loadPdf()
|
||||
// #endif
|
||||
},
|
||||
|
||||
async loadPdf() {
|
||||
// #ifdef H5
|
||||
if (!this.url) {
|
||||
this.error = '文件地址无效'
|
||||
return
|
||||
}
|
||||
|
||||
if (!pdfjsLib) {
|
||||
this.error = 'PDF组件未加载'
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
this.loadingProgress = 0
|
||||
this.error = ''
|
||||
this.clearCanvases()
|
||||
|
||||
try {
|
||||
console.log('开始加载 PDF:', this.url)
|
||||
|
||||
// 加载 PDF 文档
|
||||
const loadingTask = pdfjsLib.getDocument({
|
||||
url: this.url,
|
||||
withCredentials: false
|
||||
})
|
||||
|
||||
loadingTask.onProgress = (progress) => {
|
||||
if (progress.total > 0) {
|
||||
this.loadingProgress = Math.round((progress.loaded / progress.total) * 100)
|
||||
}
|
||||
}
|
||||
|
||||
this.pdfDoc = await loadingTask.promise
|
||||
this.totalPages = this.pdfDoc.numPages
|
||||
this.currentPage = 1
|
||||
|
||||
console.log('PDF 加载成功,总页数:', this.totalPages)
|
||||
|
||||
// 渲染第一页
|
||||
await this.renderPage(1)
|
||||
|
||||
this.loading = false
|
||||
} catch (err) {
|
||||
console.error('PDF加载失败:', err)
|
||||
this.loading = false
|
||||
|
||||
if (err.name === 'MissingPDFException') {
|
||||
this.error = '文件不存在或已被删除'
|
||||
} else if (err.message && err.message.includes('CORS')) {
|
||||
this.error = '跨域访问被拒绝,请联系管理员'
|
||||
} else if (err.message && err.message.includes('Invalid PDF')) {
|
||||
this.error = '无效的PDF文件'
|
||||
} else {
|
||||
this.error = '文件加载失败: ' + (err.message || '未知错误')
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
this.error = '当前平台不支持PDF预览'
|
||||
// #endif
|
||||
},
|
||||
|
||||
async renderPage(pageNum) {
|
||||
// #ifdef H5
|
||||
if (!this.pdfDoc) {
|
||||
console.error('pdfDoc 不存在')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('开始渲染页面:', pageNum)
|
||||
|
||||
const page = await this.pdfDoc.getPage(pageNum)
|
||||
const viewport = page.getViewport({ scale: this.scale })
|
||||
|
||||
console.log('页面尺寸:', viewport.width, 'x', viewport.height)
|
||||
|
||||
// 获取或创建 canvas
|
||||
let canvas = this.canvasElements[pageNum]
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.id = 'pdf-canvas-' + pageNum
|
||||
canvas.className = 'pdf-canvas-element'
|
||||
canvas.style.display = 'block'
|
||||
canvas.style.margin = '10px auto'
|
||||
canvas.style.backgroundColor = '#fff'
|
||||
canvas.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'
|
||||
|
||||
this.canvasElements[pageNum] = canvas
|
||||
|
||||
// 添加到容器
|
||||
const container = this.$refs.canvasContainer?.$el || this.$refs.canvasContainer
|
||||
if (container) {
|
||||
container.appendChild(canvas)
|
||||
console.log('Canvas 已添加到容器')
|
||||
} else {
|
||||
console.error('找不到 canvas 容器')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 canvas 尺寸
|
||||
canvas.width = viewport.width
|
||||
canvas.height = viewport.height
|
||||
canvas.style.width = viewport.width + 'px'
|
||||
canvas.style.height = viewport.height + 'px'
|
||||
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
// 清除之前的内容
|
||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// 渲染 PDF 页面到 canvas
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
}
|
||||
|
||||
await page.render(renderContext).promise
|
||||
console.log('页面渲染完成:', pageNum)
|
||||
|
||||
} catch (err) {
|
||||
console.error('渲染页面失败:', err)
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
clearCanvases() {
|
||||
// 清除所有 canvas
|
||||
const container = this.$refs.canvasContainer?.$el || this.$refs.canvasContainer
|
||||
if (container) {
|
||||
container.innerHTML = ''
|
||||
}
|
||||
this.canvasElements = {}
|
||||
},
|
||||
|
||||
async prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--
|
||||
this.clearCanvases()
|
||||
await this.renderPage(this.currentPage)
|
||||
}
|
||||
},
|
||||
|
||||
async nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++
|
||||
this.clearCanvases()
|
||||
await this.renderPage(this.currentPage)
|
||||
}
|
||||
},
|
||||
|
||||
async zoomIn() {
|
||||
if (this.scale < 3) {
|
||||
this.scale += 0.2
|
||||
this.clearCanvases()
|
||||
await this.renderPage(this.currentPage)
|
||||
}
|
||||
},
|
||||
|
||||
async zoomOut() {
|
||||
if (this.scale > 0.5) {
|
||||
this.scale -= 0.2
|
||||
this.clearCanvases()
|
||||
await this.renderPage(this.currentPage)
|
||||
}
|
||||
},
|
||||
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
this.clearCanvases()
|
||||
if (this.pdfDoc) {
|
||||
this.pdfDoc.destroy()
|
||||
this.pdfDoc = null
|
||||
}
|
||||
this.currentPage = 1
|
||||
this.totalPages = 0
|
||||
this.error = ''
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.cleanup()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pdf-viewer-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pdf-container {
|
||||
width: 95%;
|
||||
height: 90%;
|
||||
max-width: 900px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pdf-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px;
|
||||
background: #C93639;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pdf-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.pdf-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 13px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pdf-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.loading-wrapper,
|
||||
.error-wrapper {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-top-color: #C93639;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 8px 20px;
|
||||
background: #C93639;
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pdf-scroll-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
min-height: 100%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pdf-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
padding: 8px 16px;
|
||||
background: #C93639;
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
background: #ccc;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 全局样式,确保 canvas 正确显示 */
|
||||
.pdf-canvas-element {
|
||||
display: block !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -189,6 +189,22 @@
|
||||
"navigationBarBackgroundColor": "#C93639",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/attachment-view/attachment-view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "附件查看",
|
||||
"navigationBarBackgroundColor": "#C93639",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/event-photos/event-photos",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图片直播",
|
||||
"navigationBarBackgroundColor": "#C93639",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
903
src/pages/attachment-view/attachment-view.vue
Normal file
903
src/pages/attachment-view/attachment-view.vue
Normal file
@@ -0,0 +1,903 @@
|
||||
<template>
|
||||
<view class="attachment-page">
|
||||
<!-- 赛事信息卡片 -->
|
||||
<view class="event-info-card">
|
||||
<view class="event-title">{{ competitionName || '赛事名称' }}</view>
|
||||
<view class="event-time-row">
|
||||
<view class="time-item">
|
||||
<text class="time-label">开始时间</text>
|
||||
<text class="time-value">{{ startTime || '待定' }}</text>
|
||||
</view>
|
||||
<view class="time-divider"></view>
|
||||
<view class="time-item">
|
||||
<text class="time-label">结束时间</text>
|
||||
<text class="time-value">{{ endTime || '待定' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 附件列表 -->
|
||||
<view class="attachments-section" v-if="!loading && attachments.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="section-title">{{ pageTitle }}文件</text>
|
||||
<text class="section-count">共{{ attachments.length }}个文件</text>
|
||||
</view>
|
||||
|
||||
<view class="attachments-list">
|
||||
<view
|
||||
class="attachment-item"
|
||||
v-for="(file, index) in attachments"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 文件图标 -->
|
||||
<view class="file-icon" :class="'icon-' + file.fileType">
|
||||
<text class="icon-text">{{ getFileIconText(file.fileType) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 文件信息 -->
|
||||
<view class="file-content">
|
||||
<text class="file-name">{{ file.fileName }}</text>
|
||||
<view class="file-meta">
|
||||
<text class="meta-item">{{ file.fileSize }}</text>
|
||||
<text class="meta-dot" v-if="file.uploadTime">·</text>
|
||||
<text class="meta-item" v-if="file.uploadTime">{{ file.uploadTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="file-actions">
|
||||
<view class="action-btn preview-btn" @click="previewFile(file)">
|
||||
<text class="action-text">预览</text>
|
||||
</view>
|
||||
<view class="action-btn download-btn" @click="downloadFile(file)">
|
||||
<text class="action-text">下载</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!loading && attachments.length === 0">
|
||||
<image class="empty-image" src="/static/images/empty.png" mode="aspectFit" />
|
||||
<text class="empty-title">暂无{{ pageTitle }}文件</text>
|
||||
<text class="empty-desc">相关文件正在整理中,请稍后查看</text>
|
||||
</view>
|
||||
|
||||
<!-- PDF预览组件 (仅H5) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<pdf-viewer
|
||||
:visible="showPdfViewer"
|
||||
:url="pdfUrl"
|
||||
:file-name="pdfFileName"
|
||||
@close="closePdfViewer"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 图片预览弹窗 (仅H5) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="image-preview-modal" v-if="showImagePreview" @click="closeImagePreview">
|
||||
<view class="image-preview-container" @click.stop>
|
||||
<view class="image-preview-header">
|
||||
<text class="image-preview-title">{{ previewImageName }}</text>
|
||||
<view class="image-preview-close" @click="closeImagePreview">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="image-preview-body">
|
||||
<image :src="previewImageUrl" mode="aspectFit" class="preview-image" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import competitionAPI from '@/api/competition.js'
|
||||
// #ifdef H5
|
||||
import PdfViewer from '@/components/pdf-viewer/pdf-viewer.vue'
|
||||
// #endif
|
||||
|
||||
// 页面类型配置
|
||||
const PAGE_CONFIG = {
|
||||
'info': { title: '信息发布', type: 'info' },
|
||||
'rules': { title: '赛事规程', type: 'rules' },
|
||||
'schedule': { title: '活动日程', type: 'schedule' },
|
||||
'score': { title: '成绩公告', type: 'results' },
|
||||
'results': { title: '成绩公告', type: 'results' },
|
||||
'awards': { title: '奖牌榜', type: 'medals' },
|
||||
'medals': { title: '奖牌榜', type: 'medals' },
|
||||
'photos': { title: '图片直播', type: 'photos' }
|
||||
}
|
||||
|
||||
export default {
|
||||
// #ifdef H5
|
||||
components: {
|
||||
PdfViewer
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
pageType: 'rules',
|
||||
pageTitle: '赛事规程',
|
||||
competitionId: '',
|
||||
competitionName: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
attachments: [],
|
||||
// PDF预览相关
|
||||
showPdfViewer: false,
|
||||
pdfUrl: '',
|
||||
pdfFileName: '',
|
||||
// 图片预览相关
|
||||
showImagePreview: false,
|
||||
previewImageUrl: '',
|
||||
previewImageName: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取页面类型
|
||||
if (options.type && PAGE_CONFIG[options.type]) {
|
||||
this.pageType = options.type
|
||||
this.pageTitle = PAGE_CONFIG[options.type].title
|
||||
}
|
||||
|
||||
// 获取赛事ID
|
||||
if (options.competitionId || options.eventId) {
|
||||
this.competitionId = options.competitionId || options.eventId
|
||||
}
|
||||
|
||||
// 获取赛事名称
|
||||
if (options.name) {
|
||||
this.competitionName = decodeURIComponent(options.name)
|
||||
}
|
||||
|
||||
// 获取比赛时间
|
||||
if (options.startTime) {
|
||||
this.startTime = decodeURIComponent(options.startTime)
|
||||
}
|
||||
if (options.endTime) {
|
||||
this.endTime = decodeURIComponent(options.endTime)
|
||||
}
|
||||
|
||||
// 设置导航栏标题
|
||||
uni.setNavigationBarTitle({
|
||||
title: this.pageTitle
|
||||
})
|
||||
|
||||
// 加载数据
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
// 如果没有赛事信息,先获取赛事详情
|
||||
if (!this.startTime || !this.endTime) {
|
||||
await this.loadCompetitionInfo()
|
||||
}
|
||||
// 加载附件列表
|
||||
await this.loadAttachments()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载赛事信息
|
||||
*/
|
||||
async loadCompetitionInfo() {
|
||||
try {
|
||||
const res = await competitionAPI.getCompetitionDetail(this.competitionId)
|
||||
if (res) {
|
||||
this.competitionName = res.name || res.title || this.competitionName
|
||||
this.startTime = this.formatDate(res.startTime || res.competitionStartTime)
|
||||
this.endTime = this.formatDate(res.endTime || res.competitionEndTime)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载赛事信息失败:', err)
|
||||
// 使用模拟数据
|
||||
this.startTime = '2025.12.12'
|
||||
this.endTime = '2025.12.14'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载附件列表
|
||||
*/
|
||||
async loadAttachments() {
|
||||
try {
|
||||
// 使用 PAGE_CONFIG 映射的 type 值,而不是直接使用 pageType
|
||||
const attachmentType = PAGE_CONFIG[this.pageType]?.type || this.pageType
|
||||
console.log('=== 加载附件 ===')
|
||||
console.log('competitionId:', this.competitionId)
|
||||
console.log('pageType:', this.pageType)
|
||||
console.log('attachmentType:', attachmentType)
|
||||
|
||||
const res = await competitionAPI.getAttachments({
|
||||
competitionId: this.competitionId,
|
||||
type: attachmentType
|
||||
})
|
||||
|
||||
console.log('API返回结果:', res)
|
||||
|
||||
// 处理返回结果 - res 可能是数组或者 null/undefined
|
||||
if (res && Array.isArray(res) && res.length > 0) {
|
||||
this.attachments = res.map(file => ({
|
||||
id: file.id,
|
||||
fileName: file.fileName || file.name,
|
||||
fileUrl: file.fileUrl || file.url,
|
||||
fileSize: this.formatFileSize(file.fileSize || file.size),
|
||||
fileType: this.getFileType(file.fileName || file.name),
|
||||
uploadTime: this.formatDate(file.uploadTime || file.createTime)
|
||||
}))
|
||||
console.log('附件加载成功,共', this.attachments.length, '个文件')
|
||||
} else {
|
||||
// 空数据,显示空状态
|
||||
console.log('没有附件数据')
|
||||
this.attachments = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('=== 加载附件失败 ===')
|
||||
console.error('错误详情:', err)
|
||||
console.error('错误消息:', err.message)
|
||||
console.error('错误代码:', err.code)
|
||||
|
||||
// 只有在开发环境或者网络错误时才使用模拟数据
|
||||
// 生产环境应该显示空状态
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('开发环境,使用模拟数据')
|
||||
this.loadMockData()
|
||||
} else {
|
||||
this.attachments = []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载模拟数据
|
||||
*/
|
||||
loadMockData() {
|
||||
const mockDataMap = {
|
||||
'info': [
|
||||
{ id: '1', fileName: '2025年郑州武术大赛通知.pdf', fileUrl: '', fileSize: '1.2 MB', fileType: 'pdf', uploadTime: '2025-12-20' }
|
||||
],
|
||||
'rules': [
|
||||
{ id: '1', fileName: '2025年郑州武术大赛竞赛规程.pdf', fileUrl: '', fileSize: '2.5 MB', fileType: 'pdf', uploadTime: '2025-12-18' },
|
||||
{ id: '2', fileName: '参赛报名表.pdf', fileUrl: '', fileSize: '156 KB', fileType: 'pdf', uploadTime: '2025-12-18' }
|
||||
],
|
||||
'schedule': [
|
||||
{ id: '1', fileName: '比赛日程安排表.pdf', fileUrl: '', fileSize: '890 KB', fileType: 'pdf', uploadTime: '2025-12-19' }
|
||||
],
|
||||
'score': [
|
||||
{ id: '1', fileName: '比赛成绩公告.pdf', fileUrl: '', fileSize: '1.8 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'results': [
|
||||
{ id: '1', fileName: '比赛成绩公告.pdf', fileUrl: '', fileSize: '1.8 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'awards': [
|
||||
{ id: '1', fileName: '奖牌榜统计.pdf', fileUrl: '', fileSize: '520 KB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'medals': [
|
||||
{ id: '1', fileName: '奖牌榜统计.pdf', fileUrl: '', fileSize: '520 KB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
],
|
||||
'photos': [
|
||||
{ id: '1', fileName: '比赛精彩瞬间.pdf', fileUrl: '', fileSize: '15.6 MB', fileType: 'pdf', uploadTime: '2025-12-25' }
|
||||
]
|
||||
}
|
||||
|
||||
this.attachments = mockDataMap[this.pageType] || []
|
||||
},
|
||||
|
||||
/**
|
||||
* 预览文件
|
||||
*/
|
||||
previewFile(file) {
|
||||
if (!file.fileUrl) {
|
||||
uni.showToast({
|
||||
title: '文件暂不可用',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5端:根据文件类型选择预览方式
|
||||
if (file.fileType === 'pdf') {
|
||||
// 使用 PDF.js 组件预览
|
||||
this.pdfUrl = file.fileUrl
|
||||
this.pdfFileName = file.fileName
|
||||
this.showPdfViewer = true
|
||||
} else if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(file.fileType)) {
|
||||
// 图片使用弹窗显示
|
||||
this.previewImageUrl = file.fileUrl
|
||||
this.previewImageName = file.fileName
|
||||
this.showImagePreview = true
|
||||
} else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(file.fileType)) {
|
||||
// Office 文档使用微软在线预览
|
||||
const msViewerUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(file.fileUrl)}`
|
||||
window.open(msViewerUrl, '_blank')
|
||||
} else {
|
||||
// 其他文件类型,在新窗口打开
|
||||
window.open(file.fileUrl, '_blank')
|
||||
}
|
||||
return
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 非H5端使用下载+打开文档的方式
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
})
|
||||
|
||||
uni.downloadFile({
|
||||
url: file.fileUrl,
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
if (res.statusCode === 200) {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true,
|
||||
success: () => {
|
||||
console.log('打开文档成功')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('打开文档失败:', err)
|
||||
uni.showToast({
|
||||
title: '无法预览此文件',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '文件加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error('下载失败:', err)
|
||||
uni.showToast({
|
||||
title: '下载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭PDF预览
|
||||
*/
|
||||
closePdfViewer() {
|
||||
this.showPdfViewer = false
|
||||
this.pdfUrl = ''
|
||||
this.pdfFileName = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭图片预览
|
||||
*/
|
||||
closeImagePreview() {
|
||||
this.showImagePreview = false
|
||||
this.previewImageUrl = ''
|
||||
this.previewImageName = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
*/
|
||||
downloadFile(file) {
|
||||
if (!file.fileUrl) {
|
||||
uni.showToast({
|
||||
title: '文件暂不可用',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5端:创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = file.fileUrl
|
||||
link.download = file.fileName
|
||||
link.target = '_blank'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
uni.showToast({
|
||||
title: '开始下载',
|
||||
icon: 'success'
|
||||
})
|
||||
return
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.showLoading({
|
||||
title: '下载中...'
|
||||
})
|
||||
|
||||
uni.downloadFile({
|
||||
url: file.fileUrl,
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
if (res.statusCode === 200) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序保存文件
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
// 保存失败则打开文档
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// APP端保存到相册或文件
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.openDocument({
|
||||
filePath: res.tempFilePath,
|
||||
fileType: file.fileType,
|
||||
showMenu: true
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '下载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error('下载失败:', err)
|
||||
uni.showToast({
|
||||
title: '下载失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件类型
|
||||
*/
|
||||
getFileType(fileName) {
|
||||
if (!fileName) return 'pdf'
|
||||
const ext = fileName.split('.').pop().toLowerCase()
|
||||
return ext
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件图标文字
|
||||
*/
|
||||
getFileIconText(fileType) {
|
||||
const iconMap = {
|
||||
'pdf': 'PDF',
|
||||
'doc': 'DOC',
|
||||
'docx': 'DOC',
|
||||
'xls': 'XLS',
|
||||
'xlsx': 'XLS',
|
||||
'ppt': 'PPT',
|
||||
'pptx': 'PPT',
|
||||
'txt': 'TXT',
|
||||
'jpg': 'IMG',
|
||||
'jpeg': 'IMG',
|
||||
'png': 'IMG',
|
||||
'zip': 'ZIP',
|
||||
'rar': 'RAR'
|
||||
}
|
||||
return iconMap[fileType] || 'FILE'
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
formatFileSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
if (typeof bytes === 'string') return bytes
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i]
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return dateStr
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}.${month}.${day}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.attachment-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
// 赛事信息卡片
|
||||
.event-info-card {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.event-time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.time-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: #e0e0e0;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top-color: #C93639;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 附件区域
|
||||
.attachments-section {
|
||||
margin: 20rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.attachments-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
// 附件项
|
||||
.attachment-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
// 文件图标
|
||||
.file-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
background: #C93639;
|
||||
}
|
||||
|
||||
.file-icon.icon-pdf {
|
||||
background: #E74C3C;
|
||||
}
|
||||
|
||||
.file-icon.icon-doc,
|
||||
.file-icon.icon-docx {
|
||||
background: #3498DB;
|
||||
}
|
||||
|
||||
.file-icon.icon-xls,
|
||||
.file-icon.icon-xlsx {
|
||||
background: #27AE60;
|
||||
}
|
||||
|
||||
.file-icon.icon-ppt,
|
||||
.file-icon.icon-pptx {
|
||||
background: #E67E22;
|
||||
}
|
||||
|
||||
.file-icon.icon-jpg,
|
||||
.file-icon.icon-jpeg,
|
||||
.file-icon.icon-png {
|
||||
background: #9B59B6;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// 文件内容
|
||||
.file-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.meta-dot {
|
||||
font-size: 24rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
.file-actions {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-btn {
|
||||
background: #C93639;
|
||||
|
||||
.action-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: #fff;
|
||||
border: 1rpx solid #C93639;
|
||||
|
||||
.action-text {
|
||||
color: #C93639;
|
||||
}
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 60rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 图片预览弹窗样式
|
||||
.image-preview-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-preview-container {
|
||||
width: 95%;
|
||||
height: 90%;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #C93639;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.image-preview-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.image-preview-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.image-preview-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
res.registerTime || res.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
res.matchTime || res.competitionTime || '待定',
|
||||
registerCount: res.registrationCount || res.registerCount || res.signUpCount || '0',
|
||||
registerCount: res.registrationCount || res.registerCount || res.signUpCount || res.totalParticipants || '0',
|
||||
status: this.getStatus(res.status)
|
||||
}
|
||||
|
||||
@@ -169,16 +169,23 @@ export default {
|
||||
},
|
||||
|
||||
handleFunction(type) {
|
||||
// 需要跳转到附件展示页面的类型
|
||||
const attachmentTypes = ['info', 'rules', 'schedule', 'score', 'awards', 'photos']
|
||||
|
||||
if (attachmentTypes.includes(type)) {
|
||||
// 跳转到通用附件展示页面
|
||||
const name = encodeURIComponent(this.eventInfo.title)
|
||||
uni.navigateTo({
|
||||
url: `/pages/attachment-view/attachment-view?type=${type}&competitionId=${this.eventId}&name=${name}`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 其他功能页面的路由映射
|
||||
const routeMap = {
|
||||
'info': '/pages/event-info/event-info',
|
||||
'rules': '/pages/event-rules/event-rules',
|
||||
'schedule': '/pages/event-schedule/event-schedule',
|
||||
'players': '/pages/event-players/event-players',
|
||||
'match': '/pages/event-live/event-live',
|
||||
'lineup': '/pages/event-lineup/event-lineup',
|
||||
'score': '/pages/event-score/event-score',
|
||||
'awards': '/pages/event-medals/event-medals',
|
||||
'photos': '' // 图片直播暂未实现
|
||||
'lineup': '/pages/event-lineup/event-lineup'
|
||||
};
|
||||
|
||||
const url = routeMap[type];
|
||||
|
||||
@@ -228,7 +228,7 @@ export default {
|
||||
item.registerTime || item.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
item.matchTime || item.competitionTime || '待定',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || '0',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || item.totalParticipants || '0',
|
||||
status: this.getStatus(item.status)
|
||||
}
|
||||
})
|
||||
|
||||
43
src/pages/event-photos/event-photos.vue
Normal file
43
src/pages/event-photos/event-photos.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<view class="event-photos-page">
|
||||
<!-- 页面内容由 attachment-view 处理 -->
|
||||
<!-- 此页面作为备用,实际跳转到 attachment-view -->
|
||||
<view class="redirect-notice">
|
||||
<text>正在跳转...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
eventId: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 重定向到通用附件页面
|
||||
if (options.eventId || options.competitionId) {
|
||||
const id = options.eventId || options.competitionId
|
||||
uni.redirectTo({
|
||||
url: `/pages/attachment-view/attachment-view?type=photos&competitionId=${id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.event-photos-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.redirect-notice {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
item.registerTime || item.registrationPeriod || '待定',
|
||||
matchTime: this.formatTimeRange(startTime, endTime) ||
|
||||
item.matchTime || item.competitionTime || '待定',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || '0',
|
||||
registerCount: item.registrationCount || item.registerCount || item.signUpCount || item.totalParticipants || '0',
|
||||
status: this.getStatus(item.status)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -131,8 +131,7 @@ export default {
|
||||
|
||||
const res = await registrationAPI.getRegistrationList(params)
|
||||
|
||||
console.log('=== 我的报名列表 - 后端返回的原始数据 ===')
|
||||
console.log('完整响应:', res)
|
||||
console.log('=== 我的报名列表 ===', res)
|
||||
|
||||
let list = []
|
||||
let total = 0
|
||||
@@ -146,18 +145,18 @@ export default {
|
||||
total = res.length
|
||||
}
|
||||
|
||||
// 为每条报名记录获取详情(包含关联数据)
|
||||
const detailPromises = list.map(item => this.getRegistrationDetailData(item))
|
||||
const mappedList = await Promise.all(detailPromises)
|
||||
// 优化:先收集所有不重复的赛事ID,批量获取赛事信息(使用缓存)
|
||||
const competitionIds = [...new Set(list.map(item => item.competitionId).filter(Boolean))]
|
||||
const competitionMap = await this.batchGetCompetitionInfo(competitionIds)
|
||||
|
||||
// 过滤掉获取失败的记录
|
||||
const validList = mappedList.filter(item => item !== null)
|
||||
// 映射数据(不再单独请求赛事详情)
|
||||
const mappedList = list.map(item => this.mapRegistrationItem(item, competitionMap))
|
||||
|
||||
// 刷新或加载更多
|
||||
if (refresh || !loadMore) {
|
||||
this.eventList = validList
|
||||
this.eventList = mappedList
|
||||
} else {
|
||||
this.eventList = [...this.eventList, ...validList]
|
||||
this.eventList = [...this.eventList, ...mappedList]
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
@@ -174,56 +173,69 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单条报名记录的详细信息
|
||||
* @param {Object} orderItem 订单基本信息
|
||||
* @returns {Promise<Object>} 包含完整信息的记录
|
||||
* 批量获取赛事信息(带缓存,避免重复请求)
|
||||
* @param {Array} competitionIds 赛事ID数组
|
||||
* @returns {Promise<Object>} 赛事信息Map {id: info}
|
||||
*/
|
||||
async getRegistrationDetailData(orderItem) {
|
||||
async batchGetCompetitionInfo(competitionIds) {
|
||||
const competitionMap = {}
|
||||
|
||||
// 初始化缓存
|
||||
if (!this.competitionCache) {
|
||||
this.competitionCache = {}
|
||||
}
|
||||
|
||||
// 过滤出未缓存的赛事ID
|
||||
const uncachedIds = competitionIds.filter(id => !this.competitionCache[id])
|
||||
|
||||
// 只请求未缓存的赛事(去重后通常只有1-2个)
|
||||
if (uncachedIds.length > 0) {
|
||||
console.log('需要请求的赛事ID(去重后):', uncachedIds)
|
||||
|
||||
// 并行请求所有未缓存的赛事详情
|
||||
const promises = uncachedIds.map(async (id) => {
|
||||
try {
|
||||
console.log('=== 获取报名详情 ===', orderItem.id)
|
||||
|
||||
// 获取报名详情
|
||||
const detail = await registrationAPI.getRegistrationDetail(orderItem.id)
|
||||
console.log('报名详情:', detail)
|
||||
|
||||
// 获取赛事详情
|
||||
let competitionInfo = null
|
||||
if (orderItem.competitionId || detail.competitionId) {
|
||||
const competitionId = orderItem.competitionId || detail.competitionId
|
||||
competitionInfo = await competitionAPI.getCompetitionDetail(competitionId)
|
||||
console.log('赛事详情:', competitionInfo)
|
||||
}
|
||||
|
||||
// 构建映射数据
|
||||
const mapped = {
|
||||
id: orderItem.id,
|
||||
status: this.getStatus(orderItem.status),
|
||||
title: competitionInfo?.name || detail.competitionName || '未知赛事',
|
||||
location: competitionInfo?.location || competitionInfo?.address || detail.location || '',
|
||||
matchTime: this.formatTimeRange(
|
||||
competitionInfo?.startTime || detail.startTime,
|
||||
competitionInfo?.endTime || detail.endTime
|
||||
) || '',
|
||||
projects: detail.projectNames || this.formatProjects(detail.projects || detail.projectList) || '',
|
||||
contact: orderItem.contactPhone || detail.contactPhone || '',
|
||||
participants: detail.athleteNames || this.formatParticipants(detail.athletes || detail.athleteList) || ''
|
||||
}
|
||||
|
||||
console.log('映射后的数据:', mapped)
|
||||
return mapped
|
||||
const info = await competitionAPI.getCompetitionDetail(id)
|
||||
this.competitionCache[id] = info
|
||||
} catch (err) {
|
||||
console.error('获取报名详情失败:', err, orderItem.id)
|
||||
// 返回基本信息,避免整个记录丢失
|
||||
return {
|
||||
id: orderItem.id,
|
||||
status: this.getStatus(orderItem.status),
|
||||
title: '获取详情失败',
|
||||
location: '',
|
||||
matchTime: '',
|
||||
projects: '',
|
||||
contact: orderItem.contactPhone || '',
|
||||
participants: `${orderItem.totalParticipants || 0}人`
|
||||
console.error('获取赛事详情失败:', id, err)
|
||||
this.competitionCache[id] = null
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
// 从缓存中获取所有赛事信息
|
||||
competitionIds.forEach(id => {
|
||||
competitionMap[id] = this.competitionCache[id] || null
|
||||
})
|
||||
|
||||
return competitionMap
|
||||
},
|
||||
|
||||
/**
|
||||
* 映射单条报名记录(使用已缓存的赛事信息)
|
||||
* @param {Object} item 订单信息
|
||||
* @param {Object} competitionMap 赛事信息Map
|
||||
* @returns {Object} 映射后的数据
|
||||
*/
|
||||
mapRegistrationItem(item, competitionMap) {
|
||||
const competitionInfo = competitionMap[item.competitionId] || {}
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
competitionId: item.competitionId,
|
||||
status: this.getStatus(item.status),
|
||||
title: competitionInfo.competitionName || competitionInfo.name || item.competitionName || '未知赛事',
|
||||
location: competitionInfo.location || competitionInfo.address || item.location || '',
|
||||
matchTime: this.formatTimeRange(
|
||||
competitionInfo.competitionStartTime || competitionInfo.startTime,
|
||||
competitionInfo.competitionEndTime || competitionInfo.endTime
|
||||
) || '',
|
||||
projects: item.projectNames || this.formatProjects(item.projects) || '',
|
||||
contact: item.contactPhone || item.contactPerson || '',
|
||||
participants: item.athleteNames || `${item.totalParticipants || 0}人`
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="label">参赛人数:</text>
|
||||
<text class="value">{{ scheduleData.totalParticipants || 0 }}</text>
|
||||
<text class="value">{{ scheduleData.registerCount || 0 }}</text>
|
||||
</view>
|
||||
<view class="status-item" v-if="scheduleData.lastAutoScheduleTime">
|
||||
<text class="label">最后编排时间:</text>
|
||||
|
||||
Reference in New Issue
Block a user