web-retail-h5/pages/ticket/index.vue

1026 lines
26 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.

<!--
* @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"
title="确认撤销"
:content="cancelModalContent"
:show-cancel-button="true"
confirm-text="确认撤销"
cancel-text="取消"
@confirm="confirmCancel"
@cancel="showCancelModal = false"
></u-modal>
<!-- 修改信息弹窗 -->
<u-popup :show="editModalVisible" mode="center" border-radius="12">
<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>
<input
class="form-input"
v-model="editForm.buyName"
placeholder="请输入姓名"
/>
</view>
<view class="form-item">
<text class="form-label">手机号</text>
<input
class="form-input"
v-model="editForm.phone"
placeholder="请输入手机号"
/>
</view>
<view class="form-item">
<text class="form-label">身份证号</text>
<input
class="form-input"
v-model="editForm.idCard"
placeholder="请输入身份证号"
/>
</view>
<view class="form-item">
<text class="form-label">性别</text>
<u-radio-group v-model="editForm.sex" direction="row">
<u-radio :name="1" label="男"></u-radio>
<u-radio :name="2" label="女"></u-radio>
</u-radio-group>
</view>
<view class="form-item">
<text class="form-label">尺码</text>
<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'
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)
},
// 判断是否为当天购买
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
)
},
// 显示撤销确认弹窗
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,
})
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) {
uni.$u.toast('请选择性别')
return
}
if (!this.editForm.clothSize) {
uni.$u.toast('请输入尺码')
return
}
if (this.editForm.emergencyPhone === this.editForm.phone) {
uni.$u.toast('紧急联系方式不能与联系方式相同')
return
}
// 设置性别值
this.editForm.sexVal = this.editForm.sex === 1 ? '男' : '女'
try {
uni.showLoading({ title: '更新中...' })
const res = await updateTicket(this.editForm)
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;
}
.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);
padding: 24rpx;
min-height: calc(100vh - 100rpx);
}
.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;
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;
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;
padding: 32rpx;
box-sizing: border-box;
padding-bottom: 20px;
overflow-y: hidden;
.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>