web-base-h5/pages/ticket/buy.vue

852 lines
20 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.

<!--
* @Descripttion: 自助购票页面
* @version: 1.0.0
* @Author: Assistant
* @Date: 2025-01-22
-->
<template>
<view class="buy-ticket-container">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<view class="back-btn" @click="goBack">
<u-icon name="arrow-left" size="20" color="#333"></u-icon>
</view>
<view class="navbar-title">自助购票</view>
<view class="placeholder"></view>
</view>
</view>
<!-- 内容区域 -->
<view class="content-area">
<!-- 提示信息 -->
<view class="tip-banner">
<view class="tip-icon">
<u-icon name="info-circle" size="16" color="#f56c6c"></u-icon>
</view>
<text class="tip-text">自助购票请填写以下信息</text>
</view>
<!-- 购票人信息列表 -->
<view class="buyer-list">
<view
class="buyer-item"
v-for="(buyer, index) in buyerList"
:key="index"
>
<view class="buyer-header">
<text class="buyer-title">购票人信息{{ index + 1 }}</text>
<view
class="delete-btn"
v-if="buyerList.length > 1"
@click="removeBuyer(index)"
>
<u-icon name="trash" size="16" color="#999"></u-icon>
</view>
</view>
<view class="form-section">
<!-- 姓名 -->
<view class="form-item">
<view class="label required">
<text>姓名</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.name"
placeholder="请输入"
maxlength="20"
/>
</view>
</view>
<!-- 联系方式 -->
<view class="form-item">
<view class="label required">
<text>联系方式</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.phone"
placeholder="请输入"
type="number"
maxlength="11"
/>
</view>
</view>
<!-- 证件号码 -->
<view class="form-item">
<view class="label required">
<text>证件号码</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.idCard"
placeholder="请输入"
maxlength="18"
/>
</view>
</view>
<!-- 性别 -->
<view class="form-item">
<view class="label required">
<text>性别</text>
</view>
<view class="input-wrapper">
<picker
:value="buyer.sexIndex"
:range="sexOptions"
@change="onSexChange($event, index)"
>
<view class="picker-input">
<text
class="picker-text"
:class="{ placeholder: buyer.sexIndex === -1 }"
>
{{
buyer.sexIndex !== -1
? sexOptions[buyer.sexIndex]
: '请选择'
}}
</text>
<u-icon name="arrow-down" size="12" color="#999"></u-icon>
</view>
</picker>
</view>
</view>
<!-- 服装尺寸 -->
<view class="form-item">
<view class="label required">
<text>服装尺寸</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.clothSize"
placeholder="请输入"
maxlength="10"
/>
</view>
</view>
<!-- 同住人 -->
<view class="form-item">
<view class="label">
<text>同住人</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.cohabitant"
placeholder="请输入"
maxlength="20"
/>
</view>
</view>
<!-- 紧急联系方式 -->
<view class="form-item">
<view class="label required">
<text>紧急联系方式</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="buyer.emergencyPhone"
placeholder="请输入"
type="number"
maxlength="11"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 添加购票人按钮 -->
<view class="add-buyer-btn" @click="addBuyer">
<u-icon name="plus" size="16" color="#666"></u-icon>
<text class="add-text">继续添加购票人</text>
</view>
<!-- 提交按钮 -->
<view class="submit-wrapper">
<button
class="submit-btn"
:class="{ disabled: submitting }"
:disabled="submitting"
@click="handleSubmit"
>
{{ submitting ? '提交中...' : '提交' }}
</button>
</view>
</view>
<!-- 支付密码弹窗 -->
<u-popup
:show="showPayPwdModal"
mode="center"
border-radius="16"
width="320px"
height="auto"
>
<view class="pay-pwd-modal">
<view class="modal-header">
<view class="header-icon">
<u-icon name="lock" size="24" color="#005bac"></u-icon>
</view>
<text class="modal-title">输入支付密码</text>
<text class="modal-subtitle">请输入您的支付密码以完成购票</text>
</view>
<view class="modal-body">
<view class="pwd-input-wrapper">
<input
class="pwd-input"
v-model="payPassword"
placeholder="请输入支付密码"
type="password"
maxlength="20"
@input="onPayPwdInput"
@focus="onPwdFocus"
@blur="onPwdBlur"
/>
<view
class="input-border"
:class="{ focused: pwdInputFocused }"
></view>
</view>
</view>
<view class="modal-footer">
<view class="btn-group">
<button class="modal-btn cancel-btn" @click="closePayPwdModal">
取消
</button>
<button
class="modal-btn confirm-btn"
:class="{ disabled: !isPayPwdValid }"
:disabled="!isPayPwdValid"
@click="confirmPayPwd"
>
确认支付
</button>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { buyTicket } from '@/config/ticket.js'
export default {
data() {
return {
activityId: '',
activityName: '',
price: '',
buyerList: [
{
name: '',
phone: '',
idCard: '',
sexIndex: 1,
sex: 1,
clothSize: '',
cohabitant: '',
emergencyPhone: '',
},
],
sexOptions: ['男', '女'],
submitting: false,
showPayPwdModal: false,
payPassword: '',
pwdInputFocused: false,
}
},
computed: {
// 支付密码是否有效(非空且为纯数字)
isPayPwdValid() {
return this.payPassword.length > 0 && /^\d+$/.test(this.payPassword)
},
},
onLoad(options) {
if (options.activityId) {
this.activityId = options.activityId
}
if (options.activityName) {
this.activityName = decodeURIComponent(options.activityName)
}
if (options.price) {
this.price = options.price
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 性别选择变化
onSexChange(e, index) {
const sexIndex = e.detail.value
this.buyerList[index].sexIndex = sexIndex
this.buyerList[index].sex = sexIndex == 0 ? 1 : 2 // 1-男2-女
},
// 添加购票人
addBuyer() {
this.buyerList.push({
name: '',
phone: '',
idCard: '',
sexIndex: -1,
sex: '',
clothSize: '',
cohabitant: '',
emergencyPhone: '',
})
},
// 删除购票人
removeBuyer(index) {
uni.showModal({
title: '提示',
content: '确定删除该购票人信息吗?',
success: res => {
if (res.confirm) {
this.buyerList.splice(index, 1)
}
},
})
},
// 验证表单
validateForm() {
for (let i = 0; i < this.buyerList.length; i++) {
const buyer = this.buyerList[i]
if (!buyer.name.trim()) {
uni.$u.toast(`购票人${i + 1}的姓名不能为空`)
return false
}
if (!buyer.phone.trim()) {
uni.$u.toast(`购票人${i + 1}的联系方式不能为空`)
return false
}
if (!/^1[3-9]\d{9}$/.test(buyer.phone)) {
uni.$u.toast(`购票人${i + 1}的联系方式格式不正确`)
return false
}
if (!buyer.idCard.trim()) {
uni.$u.toast(`购票人${i + 1}的证件号码不能为空`)
return false
}
if (buyer.sexIndex === -1 || !buyer.sex) {
uni.$u.toast(`请选择购票人${i + 1}的性别`)
return false
}
if (!buyer.clothSize.trim()) {
uni.$u.toast(`购票人${i + 1}的服装尺寸不能为空`)
return false
}
if (!buyer.emergencyPhone.trim()) {
uni.$u.toast(`购票人${i + 1}的紧急联系方式不能为空`)
return false
}
if (!/^1[3-9]\d{9}$/.test(buyer.emergencyPhone)) {
uni.$u.toast(`购票人${i + 1}的紧急联系方式格式不正确`)
return false
}
}
return true
},
// 处理提交按钮点击
handleSubmit() {
if (!this.validateForm()) {
return
}
console.log('支付弹窗?')
// 显示支付密码弹窗
this.showPayPwdModal = true
this.payPassword = ''
},
// 关闭支付密码弹窗
closePayPwdModal() {
this.showPayPwdModal = false
this.payPassword = ''
},
// 支付密码输入处理
onPayPwdInput(e) {
// 只允许输入数字
this.payPassword = e.detail.value.replace(/[^\d]/g, '')
},
// 密码输入框获得焦点
onPwdFocus() {
this.pwdInputFocused = true
},
// 密码输入框失去焦点
onPwdBlur() {
this.pwdInputFocused = false
},
// 确认支付密码
confirmPayPwd() {
if (!this.isPayPwdValid) {
uni.$u.toast('请输入正确的支付密码')
return
}
this.showPayPwdModal = false
this.submitOrder()
},
// 提交订单
async submitOrder() {
if (!this.validateForm()) {
return
}
this.submitting = true
try {
// 构造提交数据
const orderData = {
payPwd: this.payPassword,
pkTicket: this.activityId,
ticketParamList: this.buyerList.map(buyer => ({
buyName: buyer.name,
phone: buyer.phone,
idCard: buyer.idCard,
sex: buyer.sex,
clothSize: buyer.clothSize,
cohabitant: buyer.cohabitant || '',
emergencyPhone: buyer.emergencyPhone,
})),
}
const res = await buyTicket(orderData)
if (res.code === 200) {
uni.showToast({
title: '购票成功',
icon: 'success',
duration: 2000,
})
setTimeout(() => {
// 返回门票列表页面并切换到我的门票tab
uni.navigateBack({
delta: 1,
})
}, 2000)
} else {
uni.$u.toast(res.msg || '购票失败,请重试')
}
} catch (error) {
console.error('购票失败:', error)
uni.$u.toast('购票失败,请重试')
} finally {
this.submitting = false
}
},
},
}
</script>
<style lang="scss" scoped>
.buy-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: 1px solid #eee;
.navbar-content {
display: flex;
align-items: center;
height: 44px;
padding: 0 16px;
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.navbar-title {
flex: 1;
text-align: center;
font-size: 18px;
font-weight: 600;
color: #333;
}
.placeholder {
width: 40px;
}
}
}
.content-area {
margin-top: calc(var(--status-bar-height) + 44px);
padding: 16px;
padding-bottom: 100px;
}
.tip-banner {
background: #fff5f5;
border: 1px solid #ffebee;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
.tip-icon {
margin-right: 8px;
}
.tip-text {
font-size: 14px;
color: #f56c6c;
}
}
.buyer-list {
.buyer-item {
background: #fff;
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.buyer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #f5f5f5;
.buyer-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.delete-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: #f8f8f8;
border-radius: 16px;
}
}
.form-section {
padding: 16px;
.form-item {
display: flex;
align-items: center;
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.label {
width: 100px;
font-size: 14px;
color: #333;
position: relative;
&.required::before {
content: '*';
color: #f56c6c;
position: absolute;
left: -10px;
}
}
.input-wrapper {
flex: 1;
.form-input {
width: 100%;
height: 44px;
background: #f8f8f8;
border-radius: 8px;
border: none;
padding: 0 16px;
font-size: 14px;
color: #333;
box-sizing: border-box;
&::placeholder {
color: #c0c0c0;
}
}
.picker-input {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 44px;
background: #f8f8f8;
box-sizing: border-box;
border-radius: 8px;
padding: 0 16px;
.picker-text {
font-size: 14px;
color: #333;
&.placeholder {
color: #c0c0c0;
}
}
}
}
}
}
}
}
.add-buyer-btn {
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
border: 2px dashed #e0e0e0;
.add-text {
font-size: 14px;
color: #666;
margin-left: 8px;
}
}
.submit-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 16px;
border-top: 1px solid #eee;
.submit-btn {
width: 100%;
height: 48px;
background: #005bac;
color: #fff;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 600;
&.disabled {
background: #ccc;
}
}
}
/* 支付密码弹窗样式 */
.pay-pwd-modal {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
.modal-header {
padding: 40rpx 40rpx 20rpx;
text-align: center;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
.header-icon {
width: 48px;
height: 48px;
background: rgba(0, 91, 172, 0.1);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 8px;
}
.modal-subtitle {
font-size: 14px;
color: #666;
display: block;
line-height: 1.4;
}
}
.modal-body {
padding: 24rpx;
.pwd-input-wrapper {
position: relative;
margin-bottom: 16rpx;
.pwd-input {
width: 100%;
height: 80rpx;
background: #f8f9fa;
border-radius: 24rpx;
border: none;
padding: 0 20rpx;
font-size: 24rpx;
color: #333;
text-align: center;
letter-spacing: 4rpx;
box-sizing: border-box;
transition: all 0.3s ease;
&::placeholder {
color: #c0c0c0;
letter-spacing: normal;
}
&:focus {
background: #fff;
outline: none;
}
}
.input-border {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2px solid transparent;
border-radius: 12px;
pointer-events: none;
transition: all 0.3s ease;
&.focused {
border-color: #005bac;
box-shadow: 0 0 0 4px rgba(0, 91, 172, 0.1);
}
}
}
.pwd-strength {
.strength-dots {
display: flex;
justify-content: center;
gap: 8px;
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background: #e9ecef;
transition: all 0.3s ease;
&.active {
background: #005bac;
transform: scale(1.2);
}
}
}
}
}
.modal-footer {
padding: 0 24px 24px;
.btn-group {
display: flex;
gap: 12px;
.modal-btn {
flex: 1;
height: 48rpx;
border: none;
border-radius: 24rpx;
font-size: 24rpx;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
&.cancel-btn {
background: #f8f9fa;
color: #666;
&:active {
background: #e9ecef;
}
}
&.confirm-btn {
background: #005bac;
color: #fff;
font-weight: 600;
&:active {
background: #004494;
}
&.disabled {
background: #e9ecef;
color: #adb5bd;
cursor: not-allowed;
&:active {
background: #e9ecef;
}
}
}
}
}
}
}
</style>