Files
martial-mini/pages/event-rules/event-rules.vue
2025-12-12 08:30:35 +08:00

557 lines
12 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>