Files
martial-mini/src/pages/event-register/event-register.vue
T
DevOpsandfactory-droid[bot] 076aa287bd fix: 报名时强制验证联系人是否已选择
- 在goToStep3方法中添加selectedContact验证
- 未选择联系人时显示提示并阻止提交
- 修复Issue #2

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-01-15 15:57:08 +08:00

1199 lines
32 KiB
Vue
Raw 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-register-page">
<!-- 步骤指示器 -->
<view class="steps-indicator">
<view class="step-item" :class="{ active: currentStep >= 1 }">
<image class="step-icon-img" :src="currentStep >= 1 ? '/static/images/选择选手信息@3x.png' : '/static/images/选择选手信息@3x.png'" mode="aspectFit"></image>
<text class="step-text">{{ isTeamProject ? '选择集体' : '选择选手信息' }}</text>
</view>
<view class="step-line" :class="{ active: currentStep >= 2 }"></view>
<view class="step-item" :class="{ active: currentStep >= 2 }">
<image class="step-icon-img" :src="currentStep >= 2 ? '/static/images/订单支付亮@3x.png' : '/static/images/订单支付灰@3x.png'" mode="aspectFit"></image>
<text class="step-text">订单支付</text>
</view>
<view class="step-line" :class="{ active: currentStep >= 3 }"></view>
<view class="step-item" :class="{ active: currentStep >= 3 }">
<image class="step-icon-img" :src="currentStep >= 3 ? '/static/images/提交报名成功亮@3x.png' : '/static/images/提交报名成功灰@3x.png'" mode="aspectFit"></image>
<text class="step-text">提交报名成功</text>
</view>
</view>
<!-- 步骤1选择选手/集体 -->
<view class="step-content" v-if="currentStep === 1">
<!-- 个人项目选择选手 -->
<template v-if="!isTeamProject">
<view class="selected-count">已选<text class="count">{{ selectedCount }}</text> </view>
<view class="add-player-btn" @click="goToAddPlayer">
<text class="add-icon"></text>
<text>新增选手</text>
</view>
<view class="player-list">
<view class="player-item" v-for="(item, index) in playerList" :key="index">
<view class="player-checkbox" @click="togglePlayer(item)">
<image v-if="item.selected" class="checkbox-img" src="/static/images/选中@3x.png" mode="aspectFit"></image>
<image v-else class="checkbox-img" src="/static/images/未选中@3x.png" mode="aspectFit"></image>
</view>
<view class="player-info">
<view class="player-name">{{ item.name }}</view>
<view class="player-id">身份证{{ item.idCard }}</view>
</view>
<view class="player-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<image class="action-icon" src="/static/images/编辑@3x.png" mode="aspectFit"></image>
<text>编辑</text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<image class="action-icon" src="/static/images/删除@3x.png" mode="aspectFit"></image>
<text>删除</text>
</view>
</view>
</view>
</view>
</template>
<!-- 集体项目选择集体 -->
<template v-else>
<view class="selected-count">已选<text class="count">{{ selectedTeamCount }}</text> 个集体</view>
<view class="add-player-btn" @click="goToAddTeam">
<text class="add-icon"></text>
<text>新增集体</text>
</view>
<view class="player-list">
<view class="player-item" v-for="(item, index) in teamList" :key="index">
<view class="player-checkbox" @click="toggleTeam(item)">
<image v-if="item.selected" class="checkbox-img" src="/static/images/选中@3x.png" mode="aspectFit"></image>
<image v-else class="checkbox-img" src="/static/images/未选中@3x.png" mode="aspectFit"></image>
</view>
<view class="player-info">
<view class="player-name">{{ item.name }}</view>
<view class="player-id">成员数{{ item.memberCount || 0 }}</view>
</view>
<view class="player-actions">
<view class="action-btn delete-btn" @click.stop="handleDeleteTeam(item)">
<image class="action-icon" src="/static/images/删除@3x.png" mode="aspectFit"></image>
<text>删除</text>
</view>
</view>
</view>
</view>
<view class="empty-state" v-if="teamList.length === 0">
<text class="empty-text">暂无集体请先新增</text>
</view>
</template>
<view class="next-btn-wrapper">
<view class="next-btn" @click="goToStep2">下一步</view>
</view>
</view>
<!-- 步骤2订单支付 -->
<view class="step-content" v-if="currentStep === 2">
<view class="order-title">订单支付</view>
<view class="event-info-card">
<view class="event-title">{{ eventInfo.title }}</view>
<view class="divider"></view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">地点</text>
<text class="value">{{ eventInfo.location }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">比赛时间</text>
<text class="value">{{ eventInfo.matchTime }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">报名项目</text>
<text class="value">{{ eventInfo.projects }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">联系人</text>
<text class="value" :class="{ placeholder: !selectedContact }">{{ selectedContact ? selectedContact.name + " " + selectedContact.phone : "请选择联系人" }}</text>
<text class="arrow"></text>
</view>
<view class="info-hint">(注意是否用此号码接收信息)</view>
<view class="info-item participants-item">
<text class="label">{{ isTeamProject ? '参赛集体:' : '参赛选手:' }}</text>
<text class="value participants" style="color: #C93639; font-weight: bold;">
{{ eventInfo.participants || (isTeamProject ? '未选择集体' : '未选择选手') }}
</text>
<view class="view-cert-btn" @click="showPlayers" v-if="!isTeamProject">
<text>查看证件</text>
<text class="arrow"></text>
</view>
</view>
</view>
<view class="payment-info">
<view class="payment-row">
<text class="label">{{ isTeamProject ? '集体数:' : '人数:' }}</text>
<text class="value">{{ isTeamProject ? selectedTeamCount : selectedCount }}</text>
</view>
<view class="payment-row total">
<text class="label">合计</text>
<text class="value price">¥ {{ totalPrice }}</text>
</view>
</view>
<view class="next-btn-wrapper">
<view class="next-btn" @click="goToStep3">去支付</view>
</view>
</view>
<!-- 步骤3报名成功 -->
<view class="step-content" v-if="currentStep === 3">
<view class="success-title">报名成功</view>
<view class="event-info-card">
<view class="event-title">{{ eventInfo.title }}</view>
<view class="divider"></view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">地点</text>
<text class="value">{{ eventInfo.location }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">比赛时间</text>
<text class="value">{{ eventInfo.matchTime }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">报名项目</text>
<text class="value">{{ eventInfo.projects }}</text>
</view>
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">联系人</text>
<text class="value" :class="{ placeholder: !selectedContact }">{{ selectedContact ? selectedContact.name + " " + selectedContact.phone : "请选择联系人" }}</text>
</view>
<template v-if="!isTeamProject">
<view class="participants-title">参赛选手{{ selectedPlayers.length }}</view>
<view class="participants-detail">
<view class="participant-item" v-for="(item, index) in selectedPlayers" :key="index">
<view class="participant-name">{{ item.name }}</view>
<view class="participant-id">身份证{{ item.idCard }}</view>
<view class="participant-number">编号{{ item.number }}</view>
</view>
</view>
</template>
<template v-else>
<view class="participants-title">参赛集体{{ selectedTeams.length }}</view>
<view class="participants-detail">
<view class="participant-item" v-for="(item, index) in selectedTeams" :key="index">
<view class="participant-name">{{ item.name }}</view>
<view class="participant-id">成员数{{ item.memberCount }}</view>
</view>
</view>
</template>
</view>
<view class="close-btn-wrapper">
<view class="close-btn" @click="handleClose"></view>
</view>
</view>
<!-- 参赛选手弹窗 -->
<view class="player-modal" v-if="showPlayerModal" @click="showPlayerModal = false">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">参赛选手</text>
<text class="close-icon" @click="showPlayerModal = false"></text>
</view>
<scroll-view class="modal-body" scroll-y>
<view class="modal-player-item" v-for="(item, index) in selectedPlayers" :key="index">
<view class="player-name">{{ item.name }}</view>
<view class="player-id">身份证{{ item.idCard }}</view>
<view class="player-number" v-if="item.number">编号{{ item.number }}</view>
<view class="player-hint" v-if="index === 0">报名成功后生成唯一编号</view>
</view>
</scroll-view>
</view>
</view>
<!-- 联系人选择弹窗 -->
<view class="contact-modal" v-if="showContactPicker" @click="showContactPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">选择联系人</text>
<text class="close-icon" @click="showContactPicker = false"></text>
</view>
<view class="add-contact-btn" @click="goToAddContact">
<text class="add-icon"></text>
<text>新增联系人</text>
</view>
<scroll-view class="modal-body contact-list" scroll-y>
<view class="contact-item" v-for="(item, index) in contactList" :key="index" @click="selectContact(item)">
<view class="contact-info">
<view class="contact-name">{{ item.name }}</view>
<view class="contact-phone">{{ item.phone }}</view>
</view>
<view class="contact-check" v-if="selectedContact && selectedContact.id === item.id"></view>
</view>
<view class="empty-state" v-if="contactList.length === 0">
<text class="empty-text">暂无联系人请先新增</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import competitionAPI from '@/api/competition.js'
import athleteAPI from '@/api/athlete.js'
import registrationAPI from '@/api/registration.js'
import { getUserInfo } from '@/utils/auth.js'
export default {
data() {
return {
currentStep: 1,
eventId: '',
selectedProjects: [],
isTeamProject: false,
eventInfo: {
title: '',
location: '',
matchTime: '',
projects: '',
contact: '',
participants: ''
},
playerList: [],
teamList: [],
selectedPlayers: [],
selectedTeams: [],
showPlayerModal: false,
showContactPicker: false,
contactList: [],
selectedContact: null,
totalPrice: 0,
registrationId: ''
};
},
computed: {
selectedCount() {
return this.playerList.filter(item => item.selected).length
},
selectedTeamCount() {
return this.teamList.filter(item => item.selected).length
}
},
onLoad(options) {
// Listen for contact added event
uni.$on('contactAdded', () => {
console.log('=== 收到 contactAdded 事件 ===')
this.loadContactList()
})
if (options.eventId) {
this.eventId = options.eventId
this.loadEventDetail(options.eventId)
}
if (options.projects) {
try {
let projectsStr = decodeURIComponent(options.projects)
if (projectsStr.includes('%')) {
projectsStr = decodeURIComponent(projectsStr)
}
this.selectedProjects = JSON.parse(projectsStr)
// Check if any project is team type (type === 2)
this.isTeamProject = this.selectedProjects.some(p => p.type === 2 || p.type === '2')
} catch (err) {
console.error('解析项目数据失败:', err)
}
}
// Load list based on project type
if (this.isTeamProject) {
this.loadTeamList()
} else {
this.loadPlayerList()
}
},
onUnload() {
uni.$off('contactAdded')
},
onShow() {
console.log('=== onShow 被触发 ===')
// Check if contact list needs refresh
const needRefresh = uni.getStorageSync('contactListNeedRefresh')
if (needRefresh) {
console.log('检测到需要刷新联系人列表')
uni.removeStorageSync('contactListNeedRefresh')
this.loadContactList()
} else {
// Always refresh contact list when page shows
this.loadContactList()
}
if (this.currentStep === 1) {
if (this.isTeamProject) {
this.loadTeamList()
} else {
this.loadPlayerList()
}
}
},
methods: {
// Contact methods
async loadContactList() {
try {
console.log('=== 开始加载联系人列表 ===')
const res = await athleteAPI.getContactList({ current: 1, size: 100 })
console.log('联系人API返回:', res)
const records = res.records || res.data?.records || []
console.log('联系人记录:', records)
this.contactList = records.map(item => ({
id: item.id,
name: item.name,
phone: item.phone,
idCard: item.idCard,
isDefault: item.isDefault
}))
console.log('处理后的联系人列表:', this.contactList)
// Auto select default contact if exists
if (!this.selectedContact && this.contactList.length > 0) {
const defaultContact = this.contactList.find(c => c.isDefault) || this.contactList[0]
this.selectedContact = defaultContact
}
} catch (err) {
console.error('加载联系人列表失败:', err)
}
},
selectContact(contact) {
this.selectedContact = contact
this.showContactPicker = false
},
goToAddContact() {
this.showContactPicker = false
uni.navigateTo({
url: '/pages/add-contact/add-contact'
})
},
async loadEventDetail(id) {
try {
const res = await competitionAPI.getCompetitionDetail(id)
const startTime = res.startTime || res.competitionStartTime || res.beginTime || res.startDate
const endTime = res.endTime || res.competitionEndTime || res.finishTime || res.endDate
let matchTime = this.formatTimeRange(startTime, endTime)
if (!matchTime && res.matchTime) {
matchTime = res.matchTime
} else if (!matchTime) {
matchTime = '待定'
}
this.eventInfo = {
title: res.name || res.title || res.competitionName || '未命名赛事',
location: res.location || res.address || res.venue || '待定',
matchTime: matchTime,
projects: this.selectedProjects && this.selectedProjects.length > 0
? this.selectedProjects.map(p => p.name).join('、')
: '',
contact: res.contactPhone || res.contact || res.phone || '',
participants: ''
}
} catch (err) {
console.error('加载赛事详情失败:', err)
this.eventInfo = {
title: '未命名赛事',
location: '待定',
matchTime: '待定',
projects: '',
contact: '',
participants: ''
}
}
},
async loadPlayerList() {
try {
const userInfo = getUserInfo()
if (!userInfo || !userInfo.userId) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
const res = await athleteAPI.getAthleteList({
current: 1,
size: 100,
createUser: userInfo.userId
})
let list = res.records || (Array.isArray(res) ? res : [])
this.playerList = list.map(item => ({
id: item.id,
name: item.name || item.athleteName || item.playerName || '未命名',
idCard: item.idCard || item.idCardNumber || '',
selected: false
}))
} catch (err) {
console.error('加载选手列表失败:', err)
}
},
async loadTeamList() {
try {
const userInfo = getUserInfo()
if (!userInfo || !userInfo.userId) return
const res = await athleteAPI.getTeamList({
current: 1,
size: 100
})
let list = res.records || (Array.isArray(res) ? res : [])
this.teamList = list.map(item => ({
id: item.id,
name: item.teamName || item.name,
memberCount: item.memberCount || 0,
selected: false
}))
} catch (err) {
console.error('加载集体列表失败:', err)
this.teamList = []
}
},
formatTimeRange(startTime, endTime) {
if (!startTime || !endTime) return ''
const formatDate = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
}
return `${formatDate(startTime)}-${formatDate(endTime)}`
},
calculateTotalPrice() {
const count = this.isTeamProject ? this.selectedTeamCount : this.selectedCount
if (!this.selectedProjects || this.selectedProjects.length === 0) return 0
const pricePerProject = this.selectedProjects.reduce((sum, p) => sum + parseFloat(p.price || 0), 0)
return (count * pricePerProject).toFixed(2)
},
togglePlayer(item) {
const index = this.playerList.findIndex(p => p.id === item.id)
if (index !== -1) {
this.$set(this.playerList[index], 'selected', !this.playerList[index].selected)
}
},
toggleTeam(item) {
const index = this.teamList.findIndex(t => t.id === item.id)
if (index !== -1) {
this.$set(this.teamList[index], 'selected', !this.teamList[index].selected)
}
},
goToAddPlayer() {
const projectIds = this.selectedProjects.map(p => p.id).join(',')
uni.navigateTo({
url: `/pages/add-player/add-player?competitionId=${this.eventId}&projectIds=${projectIds}`
})
},
goToAddTeam() {
uni.navigateTo({
url: '/pages/add-team/add-team'
})
},
handleEdit(item) {
uni.navigateTo({
url: '/pages/edit-player/edit-player?id=' + item.id
})
},
async handleDelete(item) {
try {
const confirmRes = await new Promise((resolve) => {
uni.showModal({
title: '删除选手',
content: '确定要删除该选手吗?',
success: (res) => resolve(res)
})
})
if (confirmRes.confirm) {
await athleteAPI.removeAthlete(item.id)
const index = this.playerList.findIndex(p => p.id === item.id)
if (index > -1) this.playerList.splice(index, 1)
uni.showToast({ title: '删除成功', icon: 'success' })
}
} catch (err) {
console.error('删除选手失败:', err)
uni.showToast({ title: '删除失败', icon: 'none' })
}
},
async handleDeleteTeam(item) {
try {
const confirmRes = await new Promise((resolve) => {
uni.showModal({
title: '删除集体',
content: '确定要删除该集体吗?',
success: (res) => resolve(res)
})
})
if (confirmRes.confirm) {
await athleteAPI.removeTeam(item.id)
const index = this.teamList.findIndex(t => t.id === item.id)
if (index > -1) this.teamList.splice(index, 1)
uni.showToast({ title: '删除成功', icon: 'success' })
}
} catch (err) {
console.error('删除集体失败:', err)
uni.showToast({ title: '删除失败', icon: 'none' })
}
},
goToStep2() {
if (this.isTeamProject) {
const selected = this.teamList.filter(item => item.selected)
if (selected.length === 0) {
uni.showToast({ title: '请至少选择一个集体', icon: 'none' })
return
}
this.$set(this.eventInfo, 'participants', selected.map(t => t.name).join('、'))
this.selectedTeams = selected
} else {
const selected = this.playerList.filter(item => item.selected)
if (selected.length === 0) {
uni.showToast({ title: '请至少选择一名选手', icon: 'none' })
return
}
this.$set(this.eventInfo, 'participants', selected.map(p => p.name).join('、'))
}
this.totalPrice = this.calculateTotalPrice()
this.loadContactList()
this.$nextTick(() => { this.currentStep = 2 })
},
async goToStep3() {
try {
if (!this.eventId) {
uni.showToast({ title: '赛事ID缺失', icon: 'none' })
return
}
if (!this.selectedProjects || this.selectedProjects.length === 0) {
uni.showToast({ title: '请选择报名项目', icon: 'none' })
return
}
// 强制验证联系人是否已选择
if (!this.selectedContact) {
uni.showToast({ title: '请选择联系人', icon: 'none' })
return
}
const now = new Date()
const orderNo = `BM${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}${String(Math.floor(Math.random() * 10000)).padStart(4, '0')}`
let submitData
if (this.isTeamProject) {
const selected = this.teamList.filter(item => item.selected)
if (selected.length === 0) {
uni.showToast({ title: '请选择参赛集体', icon: 'none' })
return
}
submitData = {
orderNo: orderNo,
competitionId: String(this.eventId),
projectIds: this.selectedProjects.map(p => String(p.id)),
teamIds: selected.map(t => String(t.id)),
athleteIds: [],
contactPhone: this.selectedContact ? this.selectedContact.phone : '',
totalAmount: parseFloat(this.totalPrice) || 0
}
} else {
const selected = this.playerList.filter(item => item.selected)
if (selected.length === 0) {
uni.showToast({ title: '请选择参赛选手', icon: 'none' })
return
}
submitData = {
orderNo: orderNo,
competitionId: String(this.eventId),
projectIds: this.selectedProjects.map(p => String(p.id)),
athleteIds: selected.map(p => String(p.id)),
contactPhone: this.selectedContact ? this.selectedContact.phone : '',
totalAmount: parseFloat(this.totalPrice) || 0
}
}
console.log('提交报名数据:', submitData)
const res = await registrationAPI.submitRegistration(submitData)
this.registrationId = res.id || res.registrationId || orderNo
if (this.isTeamProject) {
this.selectedTeams = this.teamList.filter(item => item.selected).map(item => ({
name: item.name,
memberCount: item.memberCount
}))
} else {
const selected = this.playerList.filter(item => item.selected)
this.selectedPlayers = selected.map((item, index) => ({
name: item.name,
idCard: item.idCard,
number: `${this.registrationId}-${String(index + 1).padStart(6, '0')}`
}))
}
this.currentStep = 3
uni.showToast({ title: '报名成功', icon: 'success' })
} catch (err) {
console.error('提交报名失败:', err)
uni.showToast({ title: '报名失败,请重试', icon: 'none' })
}
},
showPlayers() {
this.selectedPlayers = this.playerList
.filter(item => item.selected)
.map(item => ({ name: item.name, idCard: item.idCard, number: '' }))
this.showPlayerModal = true
},
handleClose() {
uni.navigateBack({ delta: 3 })
}
}
}
</script>
<style lang="scss" scoped>
.event-register-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 180rpx;
}
.steps-indicator {
background-color: #fff;
padding: 40rpx 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
opacity: 0.4;
}
.step-item.active {
opacity: 1;
}
.step-icon {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}
.step-item.active .step-icon {
background-color: #C93639;
}
.step-icon-img {
width: 70rpx;
height: 70rpx;
}
.icon {
font-size: 36rpx;
}
.step-item.active .icon {
color: #fff;
}
.step-text {
font-size: 22rpx;
color: #666666;
white-space: nowrap;
}
.step-item.active .step-text {
color: #C93639;
font-weight: bold;
}
.step-line {
flex: 1;
height: 2rpx;
background-color: #eeeeee;
margin: 0 10rpx;
}
.step-line.active {
background-color: #C93639;
}
.step-content {
padding: 30rpx;
}
.selected-count {
font-size: 28rpx;
color: #666666;
margin-bottom: 20rpx;
}
.count {
color: #C93639;
font-weight: bold;
font-size: 32rpx;
}
.add-player-btn {
background-color: #fff;
padding: 30rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
margin-bottom: 20rpx;
color: #C93639;
font-size: 30rpx;
}
.add-icon {
font-size: 36rpx;
}
.player-list {
margin-bottom: 20rpx;
}
.player-item {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
gap: 20rpx;
}
.player-checkbox {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.checkbox-img {
width: 40rpx;
height: 40rpx;
}
.checked {
font-size: 36rpx;
color: #C93639;
}
.unchecked {
font-size: 36rpx;
color: #cccccc;
}
.action-icon {
width: 28rpx;
height: 28rpx;
}
.player-info {
flex: 1;
}
.player-name {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 10rpx;
}
.player-id {
font-size: 26rpx;
color: #666666;
}
.player-actions {
display: flex;
gap: 15rpx;
}
.action-btn {
display: flex;
align-items: center;
gap: 5rpx;
padding: 10rpx 20rpx;
border-radius: 8rpx;
font-size: 24rpx;
border: 2rpx solid;
}
.edit-btn {
color: #C93639;
border-color: #C93639;
}
.delete-btn {
color: #C93639;
border-color: #C93639;
}
.next-btn-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 30rpx;
background-color: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.next-btn {
background-color: #C93639;
color: #fff;
text-align: center;
padding: 30rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: bold;
}
.order-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
margin-bottom: 30rpx;
}
.event-info-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.event-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
line-height: 1.5;
margin-bottom: 20rpx;
}
.divider {
height: 6rpx;
background-color: #C93639;
width: 120rpx;
margin: 20rpx 0;
}
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 15rpx;
}
.label {
font-size: 26rpx;
color: #666666;
flex-shrink: 0;
}
.value {
font-size: 26rpx;
color: #666666;
flex: 1;
}
.edit-icon {
margin-left: 10rpx;
font-size: 28rpx;
}
.info-hint {
font-size: 22rpx;
color: #C93639;
margin: 10rpx 0;
}
.participants-item {
align-items: center;
}
.participants {
word-break: break-all;
}
.view-cert-btn {
display: flex;
align-items: center;
padding: 8rpx 20rpx;
border: 2rpx solid #C93639;
border-radius: 8rpx;
font-size: 24rpx;
color: #C93639;
margin-left: 20rpx;
white-space: nowrap;
}
.arrow {
font-size: 24rpx;
margin-left: 5rpx;
}
.payment-info {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
}
.payment-row {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
font-size: 28rpx;
}
.payment-row.total {
font-size: 32rpx;
font-weight: bold;
}
.price {
color: #C93639;
font-size: 36rpx;
}
.success-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
margin-bottom: 30rpx;
}
.participants-title {
font-size: 28rpx;
font-weight: bold;
color: #333333;
margin: 20rpx 0;
}
.participants-detail {
margin-top: 20rpx;
}
.participant-item {
padding: 20rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
margin-bottom: 15rpx;
}
.participant-name {
font-size: 28rpx;
font-weight: bold;
color: #333333;
margin-bottom: 10rpx;
}
.participant-id {
font-size: 24rpx;
color: #666666;
margin-bottom: 5rpx;
}
.participant-number {
font-size: 24rpx;
color: #666666;
}
.close-btn-wrapper {
position: fixed;
top: 100rpx;
left: 30rpx;
z-index: 999;
}
.close-btn {
width: 80rpx;
height: 80rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #fff;
}
.player-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
width: 600rpx;
max-height: 80vh;
background-color: #fff;
border-radius: 24rpx;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eeeeee;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.close-icon {
font-size: 40rpx;
color: #999999;
}
.modal-body {
max-height: 60vh;
padding: 30rpx;
}
.modal-player-item {
padding: 20rpx;
background-color: #f9f9f9;
border-radius: 12rpx;
margin-bottom: 15rpx;
}
.player-hint {
font-size: 22rpx;
color: #C93639;
margin-top: 10rpx;
}
/* Contact Modal Styles */
.contact-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.contact-modal .modal-content {
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
width: 100%;
max-height: 70vh;
}
.contact-modal .add-contact-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
border-bottom: 1rpx solid #eee;
color: #C93639;
font-size: 28rpx;
}
.contact-modal .add-contact-btn .add-icon {
margin-right: 10rpx;
font-size: 32rpx;
}
.contact-modal .contact-list {
max-height: 50vh;
}
.contact-modal .contact-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.contact-modal .contact-info {
flex: 1;
}
.contact-modal .contact-name {
font-size: 30rpx;
color: #333;
margin-bottom: 8rpx;
}
.contact-modal .contact-phone {
font-size: 26rpx;
color: #999;
}
.contact-modal .contact-check {
color: #C93639;
font-size: 36rpx;
font-weight: bold;
}
.contact-item.clickable,
.info-item.contact-item {
cursor: pointer;
}
.info-item .placeholder {
color: #999;
}
</style>
.empty-state {
padding: 100rpx 0;
text-align: center;
}
.empty-text {
font-size: 28rpx;
color: #999999;
}