Files
xdd-uniapp-new/pkg-video/views/county-claim-address.vue
T
2026-07-02 10:06:50 +08:00

881 lines
22 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="claim-page">
<!-- <view class="custom-header" :style="{ paddingTop: `${statusBarHeight}px` }">
<view class="header-inner">
<view class="header-back" @click="goBack">
<u-icon name="arrow-left" size="24" color="#333" class="header-back-icon"></u-icon>
</view>
<view class="header-title">填写收货地址</view>
<view class="header-right">
<view class="dot"></view>
<view class="ring"></view>
</view>
</view>
</view> -->
<scroll-view class="page-body" scroll-y >
<view class="card prize-card">
<view class="section-title">奖品信息</view>
<view class="prize-content">
<view class="prize-image-wrap">
<image class="prize-image" :src="prizeImage" mode="aspectFill" />
</view>
<view class="prize-info">
<view class="prize-name">{{ form.prizeName || '神秘奖品' }}</view>
<view class="prize-desc">{{ form.prizeAttrName || '默认' }}</view>
<view class="prize-code">抽奖码{{ form.ticketCode || '--' }}</view>
</view>
</view>
</view>
<view class="card form-card">
<view class="form-card-header">
<view class="form-card-title">填写收货地址</view>
<view class="select-address-entry" @click="chooseExistingAddress">
<text class="select-address-text" style="margin-bottom: 4rpx;">选择现有地址 </text>
<u-icon name="arrow-right" size="14" color="#BD3124"></u-icon>
</view>
</view>
<view class="form-row">
<text class="label">收货人</text>
<input v-model="form.realName" class="input" placeholder="请填写收货人" placeholder-style="color:#b2b2b2;" />
</view>
<view class="form-row">
<text class="label">手机号码</text>
<input
v-model="form.phone"
class="input"
type="number"
maxlength="11"
placeholder="请填写收货手机号"
placeholder-style="color:#b2b2b2;"
/>
</view>
<view class="form-row">
<text class="label">所在地区</text>
<view class="city-wrap">
<view class="city-select" :class="{'has-val': !!addressText}">
<CitySelect
ref="cityselect"
:defaultValue="addressText || '请选择'"
:items="district"
@callback="onCityChange"
/>
</view>
<image class="locate-icon" :src="`${webUrl}/home/dingwei.png`" mode="aspectFit" @click="getCurAddressFn"></image>
</view>
</view>
<view class="form-row no-border">
<text class="label">详细地址</text>
<input v-model="form.detail" class="input" placeholder="请填写具体地址" placeholder-style="color:#b2b2b2;" />
</view>
<view class="analysis-box">
<textarea
:value="addressInput"
@input="onAddressInput"
placeholder="请粘贴收件人手机号、姓名、收货地址并用空格区分,系统将自动识别地址信息"
placeholder-style="font-size:24rpx;color:#999;"
class="analysis-textarea"
/>
<view class="analysis-actions">
<view class="action-btn btn-clear" @click="clearAddressInput">清空</view>
<view class="action-btn btn-recognize" :class="{ disabled: !addressInput }" @click="recognizeAddress">识别</view>
</view>
</view>
</view>
<view class="tips">*请仔细核对收货信息提交后不可修改奖品将在{{ shipDayText }}个工作日内寄出</view>
</scroll-view>
<view class="submit-wrap">
<view class="submit-btn" @click="submit">确认提交</view>
</view>
<view class="popup-mask" v-if="showRealnamePopup" @click="closePopup">
<view class="popup-panel auth-panel" @click.stop>
<view class="popup-close" @click="closePopup">×</view>
<view class="popup-icon-wrap">
<image class="popup-realname-icon" :src="webUrl + '/home/shiming.png'" mode="aspectFit" />
</view>
<view class="popup-title">实名认证</view>
<view class="popup-desc">根据平台活动规则领取电子数码产品需要进行实名认证</view>
<view class="popup-actions">
<view class="popup-btn btn-confirm btn-wide" @click="goRealname">去实名认证</view>
</view>
</view>
</view>
<view class="popup-mask" v-if="showSubmitSuccessPopup" @click="confirmSuccess">
<view class="popup-panel success-panel" @click.stop>
<view class="popup-close" @click="confirmSuccess">×</view>
<view class="popup-icon-wrap">
<image class="popup-success-icon" :src="webUrl + '/home/qingzhu.png'" mode="aspectFit" />
</view>
<view class="popup-title">提交成功</view>
<view class="popup-desc">奖品将在{{ shipDayText }}个工作日内寄出</view>
<view class="popup-actions">
<view class="popup-btn btn-ghost" @click="confirmSuccess">返回</view>
<view class="popup-btn btn-confirm" @click="goMyTicket">查看</view>
</view>
</view>
</view>
</view>
</template>
<script>
import CitySelect from '@/components/CitySelect'
import { getCity, analysisAddress, postAddress } from '@/api/user'
import { claimCoupon, claimPrize, getDrawRealname } from '@/api/lottery'
import { getCurAddress, getLocation } from '@/utils/common'
export default {
name: 'CountyClaimAddressPage',
components: { CitySelect },
data() {
return {
webUrl: this.$VUE_APP_RESOURCES_URL,
statusBarHeight: 20,
district: [],
addressText: '',
addressInput: '',
address: {
province: '',
city: '',
district: ''
},
form: {
lotteryRecordId: '',
prizeName: '',
prizeAttrName: '',
prizeType: '',
prizeTypeName: '',
prizeImage: '',
ticketCode: '',
realName: '',
phone: '',
detail: ''
},
showRealnamePopup: false,
showSubmitSuccessPopup: false,
shipDay: 7,
prizeInfo: {},
prizeOrderId: '',
userInfo: {},
realnameStatus: 0,
waitSubmitAfterRealname: false,
submitting: false
}
},
computed: {
prizeImage() {
return this.form.prizeImage || ''
},
shipDayText() {
const day = Number(this.shipDay)
if (!day || Number.isNaN(day) || day < 1) return 7
return day
}
},
onLoad(options) {
const routeParams = this.decodeRouteParams(options)
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 20
this.prizeInfo = routeParams
this.form = {
...this.form,
...routeParams,
lotteryRecordId: this.resolveLotteryRecordId(routeParams),
prizeName: routeParams.prizeName || '',
prizeAttrName: routeParams.prizeAttrName || '',
prizeType: routeParams.prizeType || '',
prizeTypeName: routeParams.prizeTypeName || '',
prizeImage: routeParams.prizeImage || '',
ticketCode: routeParams.ticketCode || ''
}
this.shipDay = Number(routeParams.shipDay || 0) || 7
this.getCityList()
this.fetchRealnameStatus()
},
onShow() {
this.handleChooseAddress()
const done = uni.getStorageSync('countyClaimAuthDone')
const needAutoSubmit = uni.getStorageSync('countyClaimNeedAutoSubmit')
if (done && (this.waitSubmitAfterRealname || needAutoSubmit)) {
uni.removeStorageSync('countyClaimAuthDone')
uni.removeStorageSync('countyClaimNeedAutoSubmit')
this.fetchRealnameStatus().then(() => {
this.submitPhysicalClaim()
})
}
},
methods: {
decodeRouteParams(options = {}) {
return Object.keys(options).reduce((result, key) => {
const value = options[key]
if (value === undefined || value === null) return result
try {
result[key] = decodeURIComponent(value)
} catch (e) {
result[key] = value
}
return result
}, {})
},
resolveLotteryRecordId(data = {}) {
return data.lotteryRecordId || data.id || data.ticketId || data.recordId || ''
},
goBack() {
uni.navigateBack()
},
chooseExistingAddress() {
uni.setStorageSync('sourceAddress', {
realName: this.form.realName || '',
phone: this.form.phone || '',
userAddress: `${this.address.province || ''}${this.address.city || ''}${this.address.district || ''}${this.form.detail || ''}`,
province: this.address.province || '',
city: this.address.city || '',
district: this.address.district || '',
detail: this.form.detail || ''
})
this.$yrouter.push({
path: '/pages/user/address/AddressManagement/index',
query: {
choosMode: 1
}
})
},
handleChooseAddress() {
const chooseAddress = uni.getStorageSync('chooseAddress') || {}
if (!chooseAddress.id) return
uni.setStorageSync('chooseAddress', {})
this.fillAddressForm(chooseAddress)
},
fillAddressForm(address = {}) {
this.form.realName = address.realName || address.real_name || ''
this.form.phone = address.phone || ''
this.form.detail = address.detail || ''
this.address = {
province: address.province || '',
city: address.city || '',
district: address.district || '',
city_id: address.city_id || address.cityId || address.city_id
}
this.addressText = `${this.address.province} ${this.address.city} ${this.address.district}`.trim()
},
getCityList() {
getCity().then(res => {
this.district = res.data || []
})
},
fetchRealnameStatus() {
return getDrawRealname().then((res) => {
const data = (res && res.data) || {}
this.realnameStatus = Number(data.status || 0)
}).catch(() => {
this.realnameStatus = 0
})
},
isDigitalPrize() {
const typeVal = String(this.form.prizeType || '').toLowerCase()
const typeName = String(this.form.prizeTypeName || '').toLowerCase()
const prizeName = String(this.form.prizeName || '').toLowerCase()
if (['1', 'electronic', 'digital', 'electronics'].includes(typeVal)) return true
if (/电子|数码/.test(typeName)) return true
return /电子|数码/.test(prizeName)
},
hasRealNameAuth() {
if (Number(this.realnameStatus) === 1) return true
const user = this.userInfo || {}
const flag = [
user.isRealName,
user.realNameAuth,
user.isAuth,
user.authStatus,
user.realNameStatus,
user.idAuthStatus
]
if (flag.some(v => Number(v) === 1 || v === true || String(v).toUpperCase() === 'Y')) return true
if (Number(user.authStatus) === 2 || Number(user.realNameStatus) === 2 || Number(user.idAuthStatus) === 2) return true
return !!(String(user.realName || '').trim() && String(user.idCard || user.idcard || '').trim())
},
isCouponPrize() {
const typeVal = String(this.form.prizeType || '').toLowerCase()
const typeName = String(this.form.prizeTypeName || '')
const prizeName = String(this.form.prizeName || '')
if (['3', 'coupon'].includes(typeVal)) return true
return /券/.test(typeName) || /券/.test(prizeName)
},
async createAddress() {
const payload = {
id: '',
real_name: this.form.realName,
phone: this.form.phone,
address: this.address,
detail: this.form.detail,
is_default: false,
post_code: ''
}
const res = await postAddress(payload)
const addressId = Number(res && res.data && res.data.id)
if (!addressId) {
throw new Error('地址保存失败,请重试')
}
return addressId
},
validatePhysicalForm() {
if (!this.form.realName) {
uni.showToast({ title: '请填写收货人', icon: 'none' })
return false
}
if (!/^1\d{10}$/.test(this.form.phone)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return false
}
if (!this.addressText) {
uni.showToast({ title: '请选择所在地区', icon: 'none' })
return false
}
if (!this.form.detail) {
uni.showToast({ title: '请填写详细地址', icon: 'none' })
return false
}
return true
},
async submitCouponClaim() {
const ticketId = Number(this.resolveLotteryRecordId(this.form))
if (!ticketId) {
uni.showToast({ title: '缺少抽奖券ID', icon: 'none' })
return
}
await claimCoupon(ticketId)
this.shipDay = Number(this.prizeInfo.shipDay || 0) || 7
this.prizeOrderId = ''
this.showSubmitSuccessPopup = true
},
async submitPhysicalClaim() {
if (this.submitting) return
if (!this.validatePhysicalForm()) return
const ticketId = Number(this.resolveLotteryRecordId(this.form))
if (!ticketId) {
uni.showToast({ title: '缺少抽奖券ID', icon: 'none' })
return
}
this.submitting = true
uni.showLoading({ title: '提交中...' })
try {
if (this.isDigitalPrize()) {
await this.fetchRealnameStatus()
if (!this.hasRealNameAuth()) {
this.waitSubmitAfterRealname = true
this.showRealnamePopup = true
return
}
}
const addressId = await this.createAddress()
const res = await claimPrize(ticketId, addressId)
const data = (res && res.data) || {}
this.shipDay = Number(data.shipDay || 0) || 7
this.prizeOrderId = String(data.orderId || '')
this.waitSubmitAfterRealname = false
this.showSubmitSuccessPopup = true
} catch (e) {
uni.showToast({
title: (e && e.msg) || (e && e.message) || '提交失败',
icon: 'none'
})
} finally {
uni.hideLoading()
this.submitting = false
}
},
onCityChange(values) {
this.address = {
province: values.province.name || '',
city: values.city.name || '',
district: values.district.name || '',
city_id: values.city.id
}
this.addressText = `${this.address.province} ${this.address.city} ${this.address.district}`
},
async getCurAddressFn() {
uni.showLoading({ title: '定位中...' })
try {
const map = await getLocation()
if (map.length > 1) {
const { latitude, longitude } = map[1]
getCurAddress(latitude, longitude, (addr) => {
this.address = {
province: addr.province || '',
city: addr.city || '',
district: addr.county || ''
}
this.addressText = `${this.address.province} ${this.address.city} ${this.address.district}`
this.form.detail = addr.formattedAddress || this.form.detail
})
}
} finally {
uni.hideLoading()
}
},
onAddressInput(e) {
this.addressInput = (e.detail && e.detail.value ? e.detail.value : '').trim()
},
clearAddressInput() {
this.addressInput = ''
},
recognizeAddress() {
if (!this.addressInput) return
uni.showLoading({ title: '识别中...' })
analysisAddress({ content: this.addressInput }).then((res) => {
uni.hideLoading()
const data = (res && res.data) || {}
this.form.realName = data.realName || this.form.realName
this.form.phone = data.phone || this.form.phone
this.form.detail = data.detail || this.form.detail
this.address = {
province: data.province || '',
city: data.city || '',
district: data.district || '',
city_id: data.cityId
}
this.addressText = `${this.address.province} ${this.address.city} ${this.address.district}`.trim()
}).catch(() => {
uni.hideLoading()
uni.showToast({
title: '识别失败,请手动填写',
icon: 'none'
})
})
},
async submit() {
if (this.isCouponPrize()) {
this.submitting = true
uni.showLoading({ title: '领取中...' })
try {
await this.submitCouponClaim()
} catch (e) {
uni.showToast({
title: (e && e.msg) || (e && e.message) || '领取失败',
icon: 'none'
})
} finally {
uni.hideLoading()
this.submitting = false
}
return
}
this.submitPhysicalClaim()
},
closePopup() {
this.showRealnamePopup = false
this.showSubmitSuccessPopup = false
},
goRealname() {
this.showRealnamePopup = false
uni.navigateTo({
url: '/pkg-video/views/county-realname'
})
},
confirmSuccess() {
this.showSubmitSuccessPopup = false
uni.redirectTo({
url: '/pkg-video/views/county-my-tickets'
})
},
goMyTicket() {
this.showSubmitSuccessPopup = false
uni.redirectTo({
url: `/pagesOrder/order/OrderDetails/index?id=${this.prizeOrderId}`
})
}
}
}
</script>
<style scoped lang="less">
.claim-page {
min-height: 100vh;
background: #F0F0F0;
}
.custom-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 20;
}
.header-inner {
height: 88rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
}
.header-back {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-back-icon {
font-size: 60rpx;
color: #111;
line-height: 1;
}
.header-title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: 700;
color: #222;
}
.header-right {
width: 88rpx;
height: 44rpx;
border-radius: 24rpx;
border: 2rpx solid #d8d8d8;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: #111;
margin-right: 14rpx;
}
.ring {
width: 18rpx;
height: 18rpx;
border: 2rpx solid #111;
border-radius: 50%;
}
.page-body {
height: 100vh;
box-sizing: border-box;
padding: 20rpx 24rpx 220rpx;
}
.section-title {
color: #9b9b9b;
font-size: 28rpx;
margin-bottom: 18rpx;
}
.card {
background: #fff;
border-radius: 24rpx;
margin-bottom: 24rpx;
}
.prize-card {
padding: 24rpx;
}
.prize-content {
display: flex;
align-items: center;
}
.prize-image-wrap {
width: 182rpx;
height: 182rpx;
border-radius: 16rpx;
border: 2rpx solid #f0f0f0;
margin-right: 24rpx;
overflow: hidden;
flex-shrink: 0;
}
.prize-image {
width: 100%;
height: 100%;
}
.prize-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.prize-name {
font-size: 30rpx;
font-weight: 700;
color: #2f2f2f;
margin-bottom: 12rpx;
}
.prize-desc,
.prize-code {
font-size: 28rpx;
color: #9c9c9c;
margin-top: 8rpx;
}
.form-card {
padding: 0 24rpx 24rpx;
}
.form-card-header {
min-height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2rpx dashed #e5e5e5;
}
.form-card-title {
color: #262626;
font-size: 30rpx;
font-weight: 700;
}
.select-address-entry {
display: flex;
align-items: center;
color: #BD3124;
}
.select-address-text {
font-size: 26rpx;
margin-right: 8rpx;
}
.form-row {
min-height: 92rpx;
display: flex;
align-items: center;
border-bottom: 2rpx dashed #e5e5e5;
}
.form-row.no-border {
border-bottom: none;
}
.label {
width: 180rpx;
color: #262626;
font-size: 28rpx;
font-weight: 600;
}
.input {
flex: 1;
color: #333;
font-size: 28rpx;
}
.city-wrap {
flex: 1;
display: flex;
align-items: center;
}
.city-select {
flex: 1;
color: #b2b2b2;
font-size: 28rpx;
}
.city-select.has-val {
color: #333;
}
.locate-icon {
width: 42rpx;
height: 42rpx;
margin-left: 20rpx;
flex-shrink: 0;
}
.analysis-box {
background: #f8f8f8;
border-radius: 16rpx;
padding: 20rpx 20rpx 16rpx;
margin-top: 20rpx;
}
.analysis-textarea {
width: 100%;
height: 110rpx;
color: #333;
font-size: 30rpx;
}
.analysis-actions {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 16rpx;
}
.action-btn {
font-size: 24rpx;
border-radius: 30rpx;
text-align: center;
}
.btn-clear {
color: #BD3124;
border: 2rpx solid #BD3124;
padding: 6rpx 32rpx;
margin-right: 20rpx;
background: transparent;
}
.btn-recognize {
color: #fff;
background: #BD3124;
border: 2rpx solid #BD3124;
padding: 6rpx 32rpx;
}
.btn-recognize.disabled {
opacity: 0.6;
}
.tips {
color: #2f2f2f;
font-size: 24rpx;
line-height: 1.5;
margin: 20rpx 10rpx;
}
.submit-wrap {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 40rpx calc(30rpx + env(safe-area-inset-bottom));
}
.submit-btn {
height: 92rpx;
border-radius: 46rpx;
background: #BD3124;
color: #fff;
text-align: center;
line-height: 92rpx;
font-size: 40rpx;
font-weight: 700;
letter-spacing: 18rpx;
}
.popup-mask {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.48);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
padding: 0 52rpx;
}
.popup-panel {
width: 540rpx;
height: 550rpx;
background: #fff;
border-radius: 24rpx;
padding: 40rpx 32rpx;
position: relative;
box-sizing: border-box;
}
.popup-title {
text-align: center;
color: #333;
font-size: 50rpx;
font-weight: 700;
}
.popup-desc {
color: #666;
font-size: 28rpx;
line-height: 1.6;
text-align: center;
margin-top: 30rpx;
}
.popup-close {
position: absolute;
right: 20rpx;
top: 16rpx;
width: 44rpx;
height: 44rpx;
border: 4rpx solid #333;
border-radius: 50%;
font-size: 32rpx;
line-height: 44rpx;
text-align: center;
color: #333;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.popup-icon-wrap {
display: flex;
justify-content: center;
}
.popup-icon {
font-size: 54rpx;
}
.popup-realname-icon {
width: 92rpx;
height: 92rpx;
}
.popup-success-icon {
width: 92rpx;
height: 92rpx;
}
.popup-actions {
display: flex;
align-items: center;
justify-content: center;
margin-top: 120rpx;
}
.popup-btn {
min-width: 180rpx;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
text-align: center;
font-size: 28rpx;
}
.btn-confirm {
color: #fff;
background: #BD3124;
}
.btn-wide {
width: 100%;
}
.btn-ghost {
color: #BD3124;
border: 2rpx solid #BD3124;
background: #fff;
margin-right: 22rpx;
}
</style>