557 lines
12 KiB
Vue
557 lines
12 KiB
Vue
<template>
|
||
<view class="event-rules-page">
|
||
<!-- 附件下载区 -->
|
||
<view class="attachments-section" v-if="attachments.length > 0">
|
||
<view class="section-title">
|
||
<text class="title-icon">📎</text>
|
||
<text class="title-text">规程附件</text>
|
||
</view>
|
||
<view class="attachments-list">
|
||
<view class="attachment-item" v-for="(file, index) in attachments" :key="index" @click="downloadFile(file)">
|
||
<view class="file-info">
|
||
<text class="file-icon">{{ getFileIcon(file.fileType) }}</text>
|
||
<view class="file-details">
|
||
<text class="file-name">{{ file.fileName }}</text>
|
||
<text class="file-size">{{ file.fileSize }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="download-btn">
|
||
<text class="download-icon">⬇</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 规程内容区 -->
|
||
<view class="content-section" v-if="rulesList.length > 0">
|
||
<view class="section-title">
|
||
<text class="title-icon">📄</text>
|
||
<text class="title-text">规程内容</text>
|
||
</view>
|
||
<view class="rules-list">
|
||
<view class="rules-item" v-for="(item, index) in rulesList" :key="index" @click="toggleSection(index)">
|
||
<view class="rules-header">
|
||
<view class="chapter-number">{{ item.chapter }}</view>
|
||
<view class="chapter-title">{{ item.title }}</view>
|
||
<view class="arrow" :class="{ expanded: item.expanded }">›</view>
|
||
</view>
|
||
<view class="rules-content" v-if="item.expanded">
|
||
<view class="content-item" v-for="(content, idx) in item.contents" :key="idx">
|
||
<text class="content-text">{{ content }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view class="empty-state" v-if="attachments.length === 0 && rulesList.length === 0">
|
||
<text class="empty-icon">📋</text>
|
||
<text class="empty-text">暂无规程信息</text>
|
||
</view>
|
||
|
||
<!-- 快捷入口 -->
|
||
<view class="quick-actions" v-if="eventId">
|
||
<view class="quick-action-item" @click="goToPlayers">
|
||
<view class="action-icon">👥</view>
|
||
<view class="action-text">查看参赛选手</view>
|
||
</view>
|
||
<view class="quick-action-item" @click="goToRegister">
|
||
<view class="action-icon">📝</view>
|
||
<view class="action-text">我要报名</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import competitionAPI from '@/api/competition.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
eventId: '',
|
||
// 附件列表
|
||
attachments: [],
|
||
// 规程章节列表
|
||
rulesList: []
|
||
};
|
||
},
|
||
onLoad(options) {
|
||
if (options.eventId) {
|
||
this.eventId = options.eventId
|
||
this.loadRulesData()
|
||
}
|
||
},
|
||
methods: {
|
||
/**
|
||
* 加载规程数据
|
||
*/
|
||
async loadRulesData() {
|
||
try {
|
||
// 调用API获取规程数据
|
||
const res = await competitionAPI.getCompetitionRules(this.eventId)
|
||
|
||
// 处理附件数据
|
||
if (res.attachments && res.attachments.length > 0) {
|
||
this.attachments = res.attachments.map(file => ({
|
||
id: file.id,
|
||
fileName: file.name || file.fileName,
|
||
fileUrl: file.url || file.fileUrl,
|
||
fileSize: this.formatFileSize(file.size || file.fileSize),
|
||
fileType: this.getFileType(file.name || file.fileName)
|
||
}))
|
||
}
|
||
|
||
// 处理规程内容数据
|
||
if (res.chapters && res.chapters.length > 0) {
|
||
this.rulesList = res.chapters.map(chapter => ({
|
||
chapter: chapter.chapterNumber || chapter.number,
|
||
title: chapter.title || chapter.name,
|
||
expanded: false,
|
||
contents: chapter.contents || chapter.items || []
|
||
}))
|
||
}
|
||
} catch (err) {
|
||
console.error('加载规程数据失败:', err)
|
||
// 如果API失败,使用模拟数据
|
||
this.loadMockData()
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 加载模拟数据(用于开发测试)
|
||
*/
|
||
loadMockData() {
|
||
this.attachments = [
|
||
{
|
||
id: '1',
|
||
fileName: '2025年郑州武术大赛规程.pdf',
|
||
fileUrl: 'https://example.com/rules.pdf',
|
||
fileSize: '2.5 MB',
|
||
fileType: 'pdf'
|
||
},
|
||
{
|
||
id: '2',
|
||
fileName: '参赛报名表.docx',
|
||
fileUrl: 'https://example.com/form.docx',
|
||
fileSize: '156 KB',
|
||
fileType: 'docx'
|
||
}
|
||
]
|
||
|
||
this.rulesList = [
|
||
{
|
||
chapter: '第一章',
|
||
title: '总则',
|
||
expanded: false,
|
||
contents: [
|
||
'1.1 本次比赛遵循国际武术联合会竞赛规则。',
|
||
'1.2 所有参赛选手必须持有效证件参赛。',
|
||
'1.3 参赛选手须服从裁判判决,不得有违规行为。'
|
||
]
|
||
},
|
||
{
|
||
chapter: '第二章',
|
||
title: '参赛资格',
|
||
expanded: false,
|
||
contents: [
|
||
'2.1 参赛选手年龄须在18-45周岁之间。',
|
||
'2.2 参赛选手须持有武术等级证书或相关证明。',
|
||
'2.3 参赛选手须通过健康检查,身体状况良好。'
|
||
]
|
||
},
|
||
{
|
||
chapter: '第三章',
|
||
title: '比赛规则',
|
||
expanded: false,
|
||
contents: [
|
||
'3.1 比赛采用单败淘汰制。',
|
||
'3.2 每场比赛时间为3分钟,分3局进行。',
|
||
'3.3 得分规则按照国际标准执行。'
|
||
]
|
||
},
|
||
{
|
||
chapter: '第四章',
|
||
title: '奖项设置',
|
||
expanded: false,
|
||
contents: [
|
||
'4.1 各组别设金、银、铜牌各一枚。',
|
||
'4.2 设最佳表现奖、体育道德风尚奖等特别奖项。',
|
||
'4.3 所有参赛选手均可获得参赛证书。'
|
||
]
|
||
}
|
||
]
|
||
},
|
||
|
||
/**
|
||
* 切换章节展开/收起
|
||
*/
|
||
toggleSection(index) {
|
||
this.rulesList[index].expanded = !this.rulesList[index].expanded
|
||
},
|
||
|
||
/**
|
||
* 下载文件
|
||
*/
|
||
downloadFile(file) {
|
||
uni.showLoading({
|
||
title: '准备下载'
|
||
})
|
||
|
||
// 下载文件
|
||
uni.downloadFile({
|
||
url: file.fileUrl,
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
// 保存文件到本地
|
||
const filePath = res.tempFilePath
|
||
|
||
// 打开文档
|
||
uni.openDocument({
|
||
filePath: filePath,
|
||
fileType: file.fileType,
|
||
success: () => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '打开成功',
|
||
icon: 'success'
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('打开文件失败:', err)
|
||
uni.showToast({
|
||
title: '打开失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('下载失败:', err)
|
||
uni.showToast({
|
||
title: '下载失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 获取文件类型
|
||
*/
|
||
getFileType(fileName) {
|
||
const ext = fileName.split('.').pop().toLowerCase()
|
||
return ext
|
||
},
|
||
|
||
/**
|
||
* 获取文件图标
|
||
*/
|
||
getFileIcon(fileType) {
|
||
const iconMap = {
|
||
'pdf': '📕',
|
||
'doc': '📘',
|
||
'docx': '📘',
|
||
'xls': '📗',
|
||
'xlsx': '📗',
|
||
'ppt': '📙',
|
||
'pptx': '📙',
|
||
'txt': '📄',
|
||
'zip': '📦',
|
||
'rar': '📦'
|
||
}
|
||
return iconMap[fileType] || '📄'
|
||
},
|
||
|
||
/**
|
||
* 格式化文件大小
|
||
*/
|
||
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(2) + ' ' + sizes[i]
|
||
},
|
||
|
||
/**
|
||
* 跳转到参赛选手页面
|
||
*/
|
||
goToPlayers() {
|
||
uni.navigateTo({
|
||
url: `/pages/event-players/event-players?eventId=${this.eventId}`
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 跳转到报名页面
|
||
*/
|
||
goToRegister() {
|
||
uni.navigateTo({
|
||
url: `/pages/event-register/event-register?eventId=${this.eventId}`
|
||
})
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.event-rules-page {
|
||
min-height: 100vh;
|
||
background-color: #f5f5f5;
|
||
padding: 20rpx 30rpx;
|
||
}
|
||
|
||
// 区块标题
|
||
.section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
margin-bottom: 20rpx;
|
||
padding: 0 10rpx;
|
||
}
|
||
|
||
.title-icon {
|
||
font-size: 32rpx;
|
||
}
|
||
|
||
.title-text {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
// 附件下载区
|
||
.attachments-section {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.attachments-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.attachment-item {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 25rpx 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s;
|
||
|
||
&:active {
|
||
background-color: #f8f8f8;
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
|
||
.file-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.file-icon {
|
||
font-size: 48rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.file-details {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.file-name {
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.file-size {
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
.download-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-color: #C93639;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.download-icon {
|
||
font-size: 32rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
// 规程内容区
|
||
.content-section {
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.rules-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.rules-item {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.rules-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
gap: 15rpx;
|
||
transition: background-color 0.3s;
|
||
|
||
&:active {
|
||
background-color: #f8f8f8;
|
||
}
|
||
}
|
||
|
||
.chapter-number {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #C93639;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.chapter-title {
|
||
flex: 1;
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.arrow {
|
||
font-size: 40rpx;
|
||
color: #999999;
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
.arrow.expanded {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.rules-content {
|
||
padding: 0 30rpx 30rpx;
|
||
border-top: 1rpx solid #f5f5f5;
|
||
animation: slideDown 0.3s ease;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-10rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.content-item {
|
||
margin-bottom: 15rpx;
|
||
padding-left: 20rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.content-item::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0;
|
||
top: 12rpx;
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
background-color: #C93639;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.content-text {
|
||
font-size: 26rpx;
|
||
color: #666666;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
// 空状态
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 120rpx 0;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 120rpx;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: #999999;
|
||
}
|
||
|
||
// 快捷入口
|
||
.quick-actions {
|
||
margin-top: 30rpx;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.quick-action-item {
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 15rpx;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s;
|
||
|
||
&:active {
|
||
background-color: #f8f8f8;
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 48rpx;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 26rpx;
|
||
color: #333333;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|