3
0
Fork 0
web-store-retail-h5/pages/ticket/index.vue

1026 lines
26 KiB
Vue
Raw Normal View History

2025-05-28 09:16:29 +08:00
<!--
* @Descripttion: 门票活动页面
* @version: 1.0.0
* @Author: Assistant
* @Date: 2025-01-22
-->
<template>
<view class="ticket-container">
<!-- Tab切换 -->
<view class="tab-container">
<view class="tab-wrapper">
<view
class="tab-item"
:class="{ active: currentTab === 0 }"
@click="switchTab(0)"
>
<text class="tab-text">门票活动</text>
<view class="tab-line" v-if="currentTab === 0"></view>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 1 }"
@click="switchTab(1)"
>
<text class="tab-text">我的门票</text>
<view class="tab-line" v-if="currentTab === 1"></view>
</view>
</view>
</view>
<!-- 内容区域 -->
<view class="content-area">
<!-- 门票活动列表 -->
<view v-if="currentTab === 0" class="activity-list">
<view
class="activity-card"
v-for="item in activityList"
:key="item.pkId"
>
<view class="card-content">
<view class="activity-image">
<image :src="item.actCover" mode="aspectFill"></image>
</view>
<view class="activity-info">
<view class="activity-title">{{ item.actName }}</view>
<view class="activity-detail">
<text class="detail-label">活动日期</text>
<text class="detail-value"
>{{ item.actStartDate }}~{{ item.actEndDate }}</text
>
</view>
<view class="activity-detail">
<text class="detail-label">购买日期</text>
<text class="detail-value"
>{{ item.disStartDate }}~{{ item.disEndDate }}</text
>
</view>
<view class="activity-detail">
<text class="detail-label">总票数</text>
<text class="detail-value">{{ item.quantity }}</text>
</view>
<view class="bottom-section">
<view class="price-info">
<text class="price-symbol">¥</text>
<text class="price-value">{{ item.payMoney }}</text>
</view>
<button class="buy-btn" @click="goBuyTicket(item)">购买</button>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="!activityList.length && !loading" class="empty-state">
<text class="empty-text">没有更多数据了</text>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<u-loading-icon mode="spinner"></u-loading-icon>
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 我的门票列表 -->
<view v-if="currentTab === 1" class="ticket-list">
<view class="ticket-card" v-for="item in ticketList" :key="item.pkId">
<view class="ticket-header">
<text class="order-code">订单编号{{ item.orderCode }}</text>
</view>
<view class="ticket-content">
<view class="ticket-info">
<view class="ticket-title">{{ item.actName }}</view>
<!-- 基础信息始终显示 -->
<view class="ticket-detail">
<text class="detail-label">门票价格</text>
<text class="detail-value price">¥{{ item.price }}</text>
</view>
<view class="ticket-detail">
<text class="detail-label">支付时间</text>
<text class="detail-value">{{ item.creationTime }}</text>
</view>
<view class="ticket-detail">
<text class="detail-label">支付编号</text>
<text class="detail-value">{{ item.memberCode }}</text>
</view>
<view class="ticket-detail">
<text class="detail-label">支付昵称</text>
<text class="detail-value">{{ item.memberName }}</text>
</view>
<view class="ticket-detail">
<text class="detail-label">姓名</text>
<text class="detail-value">{{ item.buyName }}</text>
</view>
<view class="ticket-detail">
<text class="detail-label">联系方式</text>
<text class="detail-value">{{ item.phone }}</text>
</view>
<!-- 展开显示的更多信息 -->
<view v-if="item.expanded" class="expanded-details">
<view class="ticket-detail" v-if="item.idCard">
<text class="detail-label">证件证号</text>
<text class="detail-value">{{ item.idCard }}</text>
</view>
<view class="ticket-detail" v-if="item.sexVal">
<text class="detail-label">性别</text>
<text class="detail-value">{{ item.sexVal }}</text>
</view>
<view class="ticket-detail" v-if="item.clothSize">
<text class="detail-label">服装尺寸</text>
<text class="detail-value">{{ item.clothSize }}</text>
</view>
<view class="ticket-detail" v-if="item.cohabitant">
<text class="detail-label">同住人</text>
<text class="detail-value">{{ item.cohabitant }}</text>
</view>
<view class="ticket-detail" v-if="item.emergencyPhone">
<text class="detail-label">紧急联系方式</text>
<text class="detail-value">{{ item.emergencyPhone }}</text>
</view>
</view>
<!-- 操作按钮仅在24小时内显示 -->
<view
v-if="isWithin24Hours(item.creationTime)"
class="action-buttons"
>
<button class="cancel-btn" @click="showCancelConfirm(item)">
撤销门票
</button>
<button class="edit-btn" @click="showEditModal(item)">
修改信息
</button>
</view>
<view class="expand-action" @click="toggleExpand(item)">
<text class="expand-text">{{
item.expanded ? '收起' : '查看更多'
}}</text>
<u-icon
:name="item.expanded ? 'arrow-up' : 'arrow-down'"
size="12"
color="#999"
></u-icon>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="!ticketList.length && !ticketLoading" class="empty-state">
<text class="empty-text">没有更多数据了</text>
</view>
<!-- 加载状态 -->
<view v-if="ticketLoading" class="loading-state">
<u-loading-icon mode="spinner"></u-loading-icon>
<text class="loading-text">加载中...</text>
</view>
</view>
</view>
<!-- 撤销确认弹窗 -->
<u-modal
:show="showCancelModal"
2025-05-28 09:16:29 +08:00
title="确认撤销"
:content="cancelModalContent"
:show-cancel-button="true"
2025-05-28 09:16:29 +08:00
confirm-text="确认撤销"
cancel-text="取消"
@confirm="confirmCancel"
@cancel="showCancelModal = false"
></u-modal>
<!-- 修改信息弹窗 -->
<u-popup :show="editModalVisible" mode="center" border-radius="12">
2025-05-28 09:16:29 +08:00
<view class="edit-modal">
<view class="modal-header">
<text class="modal-title">修改门票信息</text>
<u-icon name="close" size="20" @click="closeEditModal"></u-icon>
</view>
<scroll-view scroll-y class="modal-content">
<view class="form-item">
<text class="form-label optional">活动名称</text>
<input
class="form-input disabled"
:value="editForm.actName"
disabled
/>
</view>
<view class="form-item">
<text class="form-label optional">订单编号</text>
<input
class="form-input disabled"
:value="editForm.orderCode"
disabled
/>
</view>
<view class="form-item">
<text class="form-label">姓名</text>
2025-05-28 09:16:29 +08:00
<input
class="form-input"
v-model="editForm.buyName"
placeholder="请输入姓名"
/>
</view>
<view class="form-item">
<text class="form-label">手机号</text>
2025-05-28 09:16:29 +08:00
<input
class="form-input"
v-model="editForm.phone"
placeholder="请输入手机号"
/>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
2025-05-28 09:16:29 +08:00
<input
class="form-input"
v-model="editForm.idCard"
placeholder="请输入身份证号"
/>
</view>
<view class="form-item">
<text class="form-label">性别</text>
2025-05-28 09:16:29 +08:00
<u-radio-group v-model="editForm.sex" direction="row">
<u-radio :name="1" label="男"></u-radio>
<u-radio :name="2" label="女"></u-radio>
2025-05-28 09:16:29 +08:00
</u-radio-group>
</view>
<view class="form-item">
<text class="form-label">尺码</text>
2025-05-28 09:16:29 +08:00
<input
class="form-input"
v-model="editForm.clothSize"
placeholder="请输入尺码"
/>
</view>
<view class="form-item">
<text class="form-label optional">同住人</text>
<input
class="form-input"
v-model="editForm.cohabitant"
placeholder="请输入同住人"
/>
</view>
<view class="form-item">
<text class="form-label optional">紧急联系人</text>
<input
class="form-input"
v-model="editForm.emergencyPhone"
placeholder="请输入紧急联系人电话"
/>
</view>
</scroll-view>
<view class="modal-footer">
<button class="confirm-btn" @click="updateTicketInfo">确定</button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
getTicketActivityList,
getMyTicketList,
cancelTicket,
updateTicket,
} from '@/config/ticket.js'
2025-05-28 09:16:29 +08:00
export default {
data() {
return {
currentTab: 0,
activityList: [],
ticketList: [],
loading: false,
ticketLoading: false,
activityPage: {
pageNum: 1,
pageSize: 10,
},
ticketPage: {
pageNum: 1,
pageSize: 10,
},
// 弹窗相关
showCancelModal: false,
editModalVisible: false,
selectedTicket: null,
editForm: {
pkId: '',
actName: '',
memberCode: '',
memberName: '',
orderCode: '',
orderAmount: '',
price: '',
quantity: '',
buyName: '',
phone: '',
idCard: '',
sex: '',
sexVal: '',
clothSize: '',
cohabitant: '',
emergencyPhone: '',
creationTime: '',
},
}
},
computed: {
// 撤销弹窗内容
cancelModalContent() {
return this.selectedTicket
? `确定要撤销订单 ${this.selectedTicket.orderCode} 吗?撤销后不可恢复。`
: '确定要撤销此订单吗?撤销后不可恢复。'
},
},
onLoad() {
this.loadActivityList()
},
onPullDownRefresh() {
this.refreshCurrentTab()
},
// onReachBottom() {
// this.loadMoreData()
// },
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 切换Tab
switchTab(index) {
this.currentTab = index
if (index === 0) {
this.loadActivityList()
} else {
this.loadTicketList()
}
},
// 刷新当前Tab数据
refreshCurrentTab() {
if (this.currentTab === 0) {
this.activityPage.pageNum = 1
this.activityList = []
this.loadActivityList()
} else {
this.ticketPage.pageNum = 1
this.ticketList = []
this.loadTicketList()
}
},
// 加载更多数据
loadMoreData() {
if (this.currentTab === 0) {
this.activityPage.pageNum++
this.loadActivityList()
} else {
this.ticketPage.pageNum++
this.loadTicketList()
}
},
// 加载活动列表
async loadActivityList() {
this.loading = true
try {
const res = await getTicketActivityList(this.activityPage)
if (res.code === 200) {
if (this.activityPage.pageNum === 1) {
this.activityList = res.rows || []
} else {
this.activityList.push(...(res.rows || []))
}
}
} catch (error) {
console.error('加载活动列表失败:', error)
uni.$u.toast('加载失败,请重试')
} finally {
this.loading = false
uni.stopPullDownRefresh()
}
},
// 加载门票列表
async loadTicketList() {
this.ticketLoading = true
try {
const res = await getMyTicketList(this.ticketPage)
if (res.code === 200) {
if (this.ticketPage.pageNum === 1) {
this.ticketList = res.rows || []
} else {
this.ticketList.push(...(res.rows || []))
}
}
} catch (error) {
console.error('加载门票列表失败:', error)
uni.$u.toast('加载失败,请重试')
} finally {
this.ticketLoading = false
uni.stopPullDownRefresh()
}
},
// 去购买门票
goBuyTicket(activity) {
uni.navigateTo({
url: `/pages/ticket/buy?activityId=${activity.pkId}&activityName=${encodeURIComponent(activity.actName)}&price=${activity.payMoney}`,
})
},
// 查看门票详情
goTicketDetail(ticket) {
uni.navigateTo({
url: `/pages/ticket/detail?ticketId=${ticket.pkId}`,
})
},
// 切换展开/收起状态
toggleExpand(item) {
// 使用 $set 确保响应式更新
this.$set(item, 'expanded', !item.expanded)
},
// 判断是否为当天购买
2025-05-28 09:16:29 +08:00
isWithin24Hours(creationTime) {
if (!creationTime) return false
const createDate = new Date(creationTime)
const now = new Date()
// 获取创建日期的年月日
const createYear = createDate.getFullYear()
const createMonth = createDate.getMonth()
const createDay = createDate.getDate()
// 获取当前日期的年月日
const nowYear = now.getFullYear()
const nowMonth = now.getMonth()
const nowDay = now.getDate()
// 判断是否为同一天
return (
createYear === nowYear &&
createMonth === nowMonth &&
createDay === nowDay
)
2025-05-28 09:16:29 +08:00
},
// 显示撤销确认弹窗
showCancelConfirm(ticket) {
this.selectedTicket = ticket
this.showCancelModal = true
},
// 确认撤销门票
async confirmCancel() {
if (!this.selectedTicket) return
try {
uni.showLoading({ title: '撤销中...' })
const res = await cancelTicket({
orderCode: this.selectedTicket.orderCode,
})
2025-05-28 09:16:29 +08:00
if (res.code === 200) {
uni.$u.toast('撤销成功')
this.showCancelModal = false
this.selectedTicket = null
// 刷新列表
this.refreshCurrentTab()
} else {
uni.$u.toast(res.msg || '撤销失败')
}
} catch (error) {
console.error('撤销门票失败:', error)
uni.$u.toast('撤销失败,请重试')
} finally {
uni.hideLoading()
}
},
// 显示修改信息弹窗
showEditModal(ticket) {
this.selectedTicket = ticket
// 填充表单数据
this.editForm = {
pkId: ticket.pkId,
actName: ticket.actName,
memberCode: ticket.memberCode,
memberName: ticket.memberName,
orderCode: ticket.orderCode,
orderAmount: ticket.orderAmount,
price: ticket.price,
quantity: ticket.quantity,
buyName: ticket.buyName || '',
phone: ticket.phone || '',
idCard: ticket.idCard || '',
sex: ticket.sex || '',
sexVal: ticket.sexVal || '',
clothSize: ticket.clothSize || '',
cohabitant: ticket.cohabitant || '',
emergencyPhone: ticket.emergencyPhone || '',
creationTime: ticket.creationTime,
}
this.editModalVisible = true
},
// 关闭修改弹窗
closeEditModal() {
this.editModalVisible = false
this.selectedTicket = null
this.editForm = {
pkId: '',
actName: '',
memberCode: '',
memberName: '',
orderCode: '',
orderAmount: '',
price: '',
quantity: '',
buyName: '',
phone: '',
idCard: '',
sex: '',
sexVal: '',
clothSize: '',
cohabitant: '',
emergencyPhone: '',
creationTime: '',
}
},
// 更新门票信息
async updateTicketInfo() {
// 表单验证
if (!this.editForm.buyName) {
uni.$u.toast('请输入姓名')
return
}
if (!this.editForm.phone) {
uni.$u.toast('请输入手机号')
return
}
if (!this.editForm.idCard) {
uni.$u.toast('请输入身份证号')
return
}
if (!this.editForm.sex && this.editForm.sex !== 0) {
2025-05-28 09:16:29 +08:00
uni.$u.toast('请选择性别')
return
}
if (!this.editForm.clothSize) {
uni.$u.toast('请输入尺码')
return
}
if (this.editForm.emergencyPhone === this.editForm.phone) {
uni.$u.toast('紧急联系方式不能与联系方式相同')
return
}
2025-05-28 09:16:29 +08:00
// 设置性别值
this.editForm.sexVal = this.editForm.sex === 1 ? '男' : '女'
2025-05-28 09:16:29 +08:00
try {
uni.showLoading({ title: '更新中...' })
const res = await updateTicket(this.editForm)
2025-05-28 09:16:29 +08:00
if (res.code === 200) {
uni.$u.toast('修改成功')
this.closeEditModal()
// 刷新列表
this.refreshCurrentTab()
} else {
uni.$u.toast(res.msg || '修改失败')
}
} catch (error) {
console.error('更新门票信息失败:', error)
uni.$u.toast('修改失败,请重试')
} finally {
uni.hideLoading()
}
},
},
}
</script>
<style lang="scss" scoped>
::v-deep(.uni-scroll-view) {
box-sizing: border-box;
}
2025-05-28 09:16:29 +08:00
.ticket-container {
min-height: 100vh;
background: #f8f8f8;
}
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: #fff;
padding-top: var(--status-bar-height);
border-bottom: 2rpx solid #eee;
.navbar-content {
display: flex;
align-items: center;
height: 88rpx;
padding: 0 32rpx;
.back-btn {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
.navbar-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.placeholder {
width: 80rpx;
}
}
}
.tab-container {
position: fixed;
top: calc(var(--status-bar-height) + 88rpx);
left: 0;
right: 0;
z-index: 998;
background: #fff;
border-bottom: 2rpx solid #eee;
.tab-wrapper {
display: flex;
align-items: center;
.tab-item {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 32rpx 0;
cursor: pointer;
.tab-text {
font-size: 32rpx;
color: #666;
transition: color 0.3s;
}
.tab-line {
position: absolute;
bottom: 0;
width: 60rpx;
height: 6rpx;
background: #005bac;
border-radius: 3rpx;
}
&.active .tab-text {
color: #005bac;
font-weight: 600;
}
}
}
}
.content-area {
margin-top: calc(100rpx);
2025-05-28 09:16:29 +08:00
padding: 24rpx;
min-height: calc(100vh - 100rpx);
2025-05-28 09:16:29 +08:00
}
.activity-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
border: 1rpx solid #f0f0f0;
.card-content {
display: flex;
padding: 24rpx;
gap: 20rpx;
.activity-image {
flex-shrink: 0;
width: 208rpx;
height: 208rpx;
border-radius: 40rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.activity-info {
flex: 1;
display: flex;
flex-direction: column;
.activity-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.4;
margin-bottom: 16rpx;
}
.activity-detail {
display: flex;
margin-bottom: 8rpx;
.detail-label {
font-size: 20rpx;
color: #999;
min-width: 140rpx;
}
.detail-value {
font-size: 20rpx;
color: #666;
flex: 1;
}
}
.bottom-section {
display: flex;
justify-content: space-between;
align-items: center;
.price-info {
display: flex;
align-items: baseline;
color: #ff4444;
.price-symbol {
font-size: 24rpx;
margin-right: 2rpx;
}
.price-value {
font-size: 32rpx;
font-weight: 700;
}
}
.buy-btn {
background: #005bac;
color: #fff;
border: none;
border-radius: 40rpx;
padding: 0rpx 32rpx;
font-size: 20rpx;
font-weight: 600;
margin: 0;
}
}
}
}
}
.ticket-card {
background: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
.ticket-header {
margin-bottom: 16rpx;
.order-code {
font-size: 24rpx;
color: #999;
}
}
.ticket-content {
.ticket-info {
.ticket-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
line-height: 1.3;
}
.ticket-detail {
display: flex;
margin-bottom: 12rpx;
.detail-label {
font-size: 24rpx;
color: #999;
min-width: 140rpx;
}
.detail-value {
font-size: 24rpx;
color: #666;
&.price {
color: #ff4444;
2025-05-28 09:16:29 +08:00
font-weight: 600;
}
}
}
.expanded-details {
animation: fadeIn 0.3s ease-in-out;
.ticket-detail {
margin-bottom: 12rpx;
}
}
.action-buttons {
display: flex;
gap: 16rpx;
margin-top: 16rpx;
padding: 16rpx 0;
.cancel-btn,
.edit-btn {
flex: 1;
height: 60rpx;
border: none;
border-radius: 30rpx;
font-size: 24rpx;
font-weight: 600;
margin: 0;
}
.cancel-btn {
background: #fff;
color: #ff4444;
border: 2rpx solid #ff4444;
}
.edit-btn {
background: #005bac;
color: #fff;
}
}
.expand-action {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16rpx;
padding: 16rpx 0;
border-top: 1rpx solid #eee;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background-color: #f8f8f8;
}
.expand-text {
font-size: 24rpx;
color: #999;
margin-right: 8rpx;
}
}
}
}
}
.empty-state,
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-text,
.loading-text {
font-size: 28rpx;
color: #999;
margin-top: 16rpx;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// 修改信息弹窗样式
.edit-modal {
width: 640rpx;
max-height: 90vh;
2025-05-28 09:16:29 +08:00
background: #fff;
border-radius: 24rpx;
overflow: hidden;
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 2rpx solid #eee;
.modal-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.modal-content {
max-height: 70vh;
2025-05-28 09:16:29 +08:00
padding: 32rpx;
box-sizing: border-box;
padding-bottom: 20px;
overflow-y: hidden;
2025-05-28 09:16:29 +08:00
.form-item {
margin-bottom: 32rpx;
.form-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
&::after {
content: '*';
color: #ff4444;
margin-left: 4rpx;
}
&.optional::after {
display: none;
}
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
border: 2rpx solid #ddd;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
background: #fff;
box-sizing: border-box;
&.disabled {
background: #f5f5f5;
color: #999;
}
&:focus {
border-color: #005bac;
}
}
}
}
.modal-footer {
padding: 32rpx;
border-top: 2rpx solid #eee;
.confirm-btn {
width: 100%;
height: 80rpx;
background: #005bac;
color: #fff;
border: none;
border-radius: 40rpx;
font-size: 32rpx;
font-weight: 600;
margin: 0;
}
}
}
</style>