2026-01-06 17:26:55 +08:00

394 lines
8.9 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="container">
<view class="header glass-card">
<view class="title">填写收货信息</view>
<view class="desc">好友正在为您申请奖品发货请填写您的准确收货地址</view>
</view>
<!-- 已登录用户显示地址列表 -->
<view v-if="isLoggedIn && addressList.length > 0" class="address-list-section">
<view class="section-title">选择已保存的地址</view>
<view class="address-list">
<view
v-for="(addr, index) in addressList"
:key="addr.id || index"
class="address-card"
:class="{ selected: selectedAddressIndex === index }"
@tap="selectAddress(index)"
>
<view class="address-info">
<view class="address-header">
<text class="name">{{ addr.name }}</text>
<text class="mobile">{{ addr.mobile }}</text>
</view>
<view class="address-detail">
{{ addr.province }} {{ addr.city }} {{ addr.district }} {{ addr.address }}
</view>
</view>
<view class="address-check" v-if="selectedAddressIndex === index">
<text class="check-icon"></text>
</view>
</view>
</view>
<view class="divider">
<text class="divider-text">或填写新地址</text>
</view>
</view>
<view class="form glass-card">
<view class="form-item">
<text class="label">收货人</text>
<input v-model="form.name" placeholder="请输入姓名" class="input" />
</view>
<view class="form-item">
<text class="label">手机号码</text>
<input v-model="form.mobile" type="number" maxlength="11" placeholder="请输入手机号" class="input" />
</view>
<view class="form-item">
<text class="label">地区</text>
<picker mode="region" @change="onRegionChange" class="input">
<view class="picker-value" v-if="form.province">
{{ form.province }} {{ form.city }} {{ form.district }}
</view>
<view class="picker-placeholder" v-else>请选择省市区</view>
</picker>
</view>
<view class="form-item">
<text class="label">详细地址</text>
<textarea v-model="form.address" placeholder="街道、楼牌号等" class="textarea" />
</view>
</view>
<view class="footer-btn">
<button class="submit-btn" :loading="loading" @tap="onSubmit">确认提交</button>
</view>
<view class="tip-section">
<text class="tip-text">* 请确保信息准确提交后无法修改</text>
<text class="tip-text" v-if="isLoggedIn">* 您已登录提交后该奖品将转移至您的账户下</text>
<text class="tip-text" v-else>* 您当前未登录提交后资产仍归属于原发起人</text>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { request } from '@/utils/request'
import { listAddresses } from '@/api/appUser'
const token = ref('')
const loading = ref(false)
const isLoggedIn = ref(!!uni.getStorageSync('token'))
const addressList = ref([])
const selectedAddressIndex = ref(-1)
const form = reactive({
name: '',
mobile: '',
province: '',
city: '',
district: '',
address: ''
})
onLoad((options) => {
if (options.token) {
token.value = options.token
// 如果已登录,加载用户的地址列表
if (isLoggedIn.value) {
loadAddressList()
}
} else {
uni.showToast({ title: '参数错误', icon: 'none' })
}
})
// 加载用户地址列表
async function loadAddressList() {
if (!isLoggedIn.value) return
try {
const userId = uni.getStorageSync('user_id')
if (!userId) return
const res = await listAddresses(userId)
addressList.value = res.list || res.data || res || []
} catch (e) {
console.error('获取地址列表失败:', e)
addressList.value = []
}
}
// 选择地址
function selectAddress(index) {
selectedAddressIndex.value = index
const addr = addressList.value[index]
if (addr) {
form.name = addr.name || ''
form.mobile = addr.mobile || ''
form.province = addr.province || ''
form.city = addr.city || ''
form.district = addr.district || ''
form.address = addr.address || ''
}
}
function onRegionChange(e) {
const [p, c, d] = e.detail.value
form.province = p
form.city = c
form.district = d
// 用户手动修改地区时,清除地址选择状态
selectedAddressIndex.value = -1
}
async function onSubmit() {
if (!token.value) return
if (!form.name || !form.mobile || !form.province || !form.address) {
uni.showToast({ title: '请完善收货信息', icon: 'none' })
return
}
if (!/^1\d{10}$/.test(form.mobile)) {
uni.showToast({ title: '手机号格式错误', icon: 'none' })
return
}
loading.value = true
try {
const res = await request({
url: '/api/app/address-share/submit',
method: 'POST',
data: {
share_token: token.value,
...form
},
// 手动带上 token因为公共 request 可能不带
header: {
'Authorization': uni.getStorageSync('token') || ''
}
})
uni.showModal({
title: '提交成功',
content: '收货信息已提交,请等待发货。' + (isLoggedIn.value ? '资产已转移至您的盒柜。' : ''),
showCancel: false,
success: () => {
// #ifdef MP-TOUTIAO
// 抖音平台跳转到商城
uni.switchTab({ url: '/pages/shop/index' })
// #endif
// #ifndef MP-TOUTIAO
uni.switchTab({ url: '/pages/index/index' })
// #endif
}
})
} catch (e) {
uni.showToast({ title: e.message || '提交失败', icon: 'none' })
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 30rpx;
min-height: 100vh;
background: $bg-page;
}
.header {
padding: 40rpx;
margin-bottom: 30rpx;
animation: fadeInDown 0.5s ease-out;
.title {
font-size: 36rpx;
font-weight: 700;
color: $text-main;
margin-bottom: 16rpx;
}
.desc {
font-size: 26rpx;
color: $text-sub;
line-height: 1.5;
}
}
/* 地址列表部分 */
.address-list-section {
margin-bottom: 30rpx;
animation: fadeInUp 0.5s ease-out 0.1s backwards;
}
.section-title {
font-size: 28rpx;
color: $text-main;
font-weight: 600;
margin-bottom: 20rpx;
padding: 0 10rpx;
}
.address-list {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 30rpx;
}
.address-card {
background: #fff;
border-radius: $radius-lg;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: $shadow-sm;
border: 2rpx solid transparent;
transition: all 0.3s;
&.selected {
border-color: $brand-primary;
background: rgba($brand-primary, 0.03);
}
&:active {
transform: scale(0.98);
}
}
.address-info {
flex: 1;
margin-right: 20rpx;
}
.address-header {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 12rpx;
.name {
font-size: 30rpx;
font-weight: 600;
color: $text-main;
}
.mobile {
font-size: 26rpx;
color: $text-sub;
}
}
.address-detail {
font-size: 26rpx;
color: $text-secondary;
line-height: 1.5;
}
.address-check {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background: $brand-primary;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.check-icon {
color: #fff;
font-size: 28rpx;
font-weight: bold;
}
}
.divider {
display: flex;
align-items: center;
margin: 30rpx 0;
&::before,
&::after {
content: '';
flex: 1;
height: 1rpx;
background: rgba(0, 0, 0, 0.1);
}
.divider-text {
padding: 0 20rpx;
font-size: 24rpx;
color: $text-tertiary;
}
}
.form {
padding: 20rpx 40rpx;
animation: fadeInUp 0.5s ease-out 0.2s backwards;
}
.form-item {
padding: 30rpx 0;
border-bottom: 1rpx solid rgba(0,0,0,0.05);
&:last-child { border-bottom: none; }
.label {
display: block;
font-size: 28rpx;
color: $text-main;
margin-bottom: 20rpx;
font-weight: 600;
}
.input, .textarea {
width: 100%;
font-size: 28rpx;
color: $text-main;
}
.textarea {
height: 160rpx;
padding: 0;
}
.picker-placeholder { color: $text-tertiary; }
}
.footer-btn {
margin-top: 60rpx;
padding: 0 40rpx;
}
.submit-btn {
height: 88rpx;
background: $gradient-brand;
color: #fff;
border-radius: $radius-round;
font-size: 32rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-warm;
&:active { transform: scale(0.98); opacity: 0.9; }
}
.tip-section {
margin-top: 40rpx;
padding: 0 40rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.tip-text {
font-size: 22rpx;
color: $text-tertiary;
}
</style>