feat: add contact management and various bug fixes

- Add contact API methods in athlete.js
- Add contact list display in common-info.vue
- Update add-contact.vue for contact creation
- Create edit-contact page for contact editing
- Fix event-register.vue with contact picker modal
- Fix home.vue registration status display
- Fix my-registration.vue cert modal display

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
DevOps
2026-01-05 15:12:04 +08:00
co-authored by factory-droid[bot]
parent 4eddc5a194
commit 13eb311575
8 changed files with 720 additions and 489 deletions
+153 -13
View File
@@ -98,22 +98,22 @@
<view class="event-info-card">
<view class="event-title">{{ eventInfo.title }}</view>
<view class="divider"></view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">地点</text>
<text class="value">{{ eventInfo.location }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">比赛时间</text>
<text class="value">{{ eventInfo.matchTime }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">报名项目</text>
<text class="value">{{ eventInfo.projects }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">联系人</text>
<text class="value">{{ eventInfo.contact }}</text>
<text class="edit-icon">📋</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">
@@ -151,21 +151,21 @@
<view class="event-info-card">
<view class="event-title">{{ eventInfo.title }}</view>
<view class="divider"></view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">地点</text>
<text class="value">{{ eventInfo.location }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">比赛时间</text>
<text class="value">{{ eventInfo.matchTime }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">报名项目</text>
<text class="value">{{ eventInfo.projects }}</text>
</view>
<view class="info-item">
<view class="info-item contact-item" @click="showContactPicker = true">
<text class="label">联系人</text>
<text class="value">{{ eventInfo.contact }}</text>
<text class="value" :class="{ placeholder: !selectedContact }">{{ selectedContact ? selectedContact.name + " " + selectedContact.phone : "请选择联系人" }}</text>
</view>
<template v-if="!isTeamProject">
@@ -211,6 +211,32 @@
</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>
@@ -240,6 +266,9 @@ export default {
selectedPlayers: [],
selectedTeams: [],
showPlayerModal: false,
showContactPicker: false,
contactList: [],
selectedContact: null,
totalPrice: 0,
registrationId: ''
};
@@ -290,6 +319,38 @@ export default {
}
},
methods: {
// Contact methods
async loadContactList() {
try {
const res = await athleteAPI.getContactList({ current: 1, size: 100 })
this.contactList = (res.records || []).map(item => ({
id: item.id,
name: item.name,
phone: item.phone,
idCard: item.idCard
}))
// 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)
@@ -487,6 +548,7 @@ export default {
}
this.totalPrice = this.calculateTotalPrice()
this.loadContactList()
this.$nextTick(() => { this.currentStep = 2 })
},
@@ -518,7 +580,7 @@ export default {
projectIds: this.selectedProjects.map(p => String(p.id)),
teamIds: selected.map(t => String(t.id)),
athleteIds: [],
contactPhone: this.eventInfo.contact || '',
contactPhone: this.selectedContact ? this.selectedContact.phone : '',
totalAmount: parseFloat(this.totalPrice) || 0
}
} else {
@@ -532,7 +594,7 @@ export default {
competitionId: String(this.eventId),
projectIds: this.selectedProjects.map(p => String(p.id)),
athleteIds: selected.map(p => String(p.id)),
contactPhone: this.eventInfo.contact || '',
contactPhone: this.selectedContact ? this.selectedContact.phone : '',
totalAmount: parseFloat(this.totalPrice) || 0
}
}
@@ -1014,6 +1076,84 @@ export default {
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 {