This commit is contained in:
@@ -1,83 +1,158 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view v-if="!exchangeData && !loading" class="empty-state">
|
||||
<view class="empty-icon">🔄</view>
|
||||
<view class="empty-icon">📧</view>
|
||||
<view class="empty-title">暂无换货记录</view>
|
||||
<view class="empty-desc">当前账号下暂无设备换货记录</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="exchangeData" class="card exchange-card">
|
||||
<view class="exchange-header flex-row-sb">
|
||||
<view class="exchange-no">{{ exchangeData?.exchange_no }}</view>
|
||||
<view class="tag-apple" :class="getStatusClass(exchangeData?.status)">{{ exchangeData?.status_text }}</view>
|
||||
<view class="exchange-no">{{ exchangeData.exchange_no }}</view>
|
||||
<view class="tag-apple" :class="getStatusClass(exchangeData.status)">
|
||||
{{ exchangeData.status_text }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exchange-info">
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">换货原因</view>
|
||||
<view class="info-value">{{ exchangeData?.exchange_reason }}</view>
|
||||
<view class="info-value">{{ exchangeData.exchange_reason }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb">
|
||||
<view class="info-label">申请时间</view>
|
||||
<view class="info-value">{{ formatTime(exchangeData?.created_at) }}</view>
|
||||
<view class="info-value">{{ formatTime(exchangeData.created_at) }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_name">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_name">
|
||||
<view class="info-label">收件人</view>
|
||||
<view class="info-value">{{ exchangeData?.recipient_name }}</view>
|
||||
<view class="info-value">{{ exchangeData.recipient_name }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_phone">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_phone">
|
||||
<view class="info-label">联系电话</view>
|
||||
<view class="info-value">{{ exchangeData?.recipient_phone }}</view>
|
||||
<view class="info-value">{{ exchangeData.recipient_phone }}</view>
|
||||
</view>
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData?.recipient_address">
|
||||
<view class="info-row flex-row-sb" v-if="exchangeData.recipient_address">
|
||||
<view class="info-label">收货地址</view>
|
||||
<view class="info-value address-value">{{ exchangeData?.recipient_address }}</view>
|
||||
<view class="info-value address-value">{{ exchangeData.recipient_address }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exchange-actions" v-if="exchangeData?.status === 1">
|
||||
<view class="exchange-actions" v-if="exchangeData.status === 1">
|
||||
<button class="btn-apple btn-primary" @tap="openPopup">填写信息</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<up-popup :show="showPopup" mode="center" @close="showPopup=false">
|
||||
<up-popup :show="showPopup" mode="center" @close="handlePopupClose">
|
||||
<view class="popup-content">
|
||||
<view class="popup-title">填写换货信息</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">粘贴收货信息</view>
|
||||
<up-textarea
|
||||
v-model="form.raw_shipping_text"
|
||||
placeholder="可粘贴包含收件人、电话、地址的一整段文字"
|
||||
autoHeight
|
||||
:maxlength="-1"
|
||||
border="surround"
|
||||
/>
|
||||
<view class="form-helper">识别结果可继续手动修改</view>
|
||||
<button class="btn-apple btn-secondary parse-btn" @tap="fillFromPastedText">
|
||||
识别并填充
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收件人姓名</view>
|
||||
<up-input placeholder="请输入收件人姓名" border="surround" v-model="form.recipient_name" />
|
||||
<up-input
|
||||
v-model="form.recipient_name"
|
||||
placeholder="请输入收件人姓名"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收件人电话</view>
|
||||
<up-input placeholder="请输入收件人电话" border="surround" v-model="form.recipient_phone" />
|
||||
<up-input
|
||||
v-model="form.recipient_phone"
|
||||
placeholder="请输入收件人电话"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">收货地址</view>
|
||||
<up-input placeholder="请输入详细收货地址" border="surround" v-model="form.recipient_address" />
|
||||
<view class="form-label">所在地区</view>
|
||||
<view class="region-trigger" @tap="openAreaPicker">
|
||||
<text class="region-trigger-text" :class="{ placeholder: !regionDisplayText }">
|
||||
{{ regionDisplayText || '请选择省 / 市 / 区' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">详细地址</view>
|
||||
<up-textarea
|
||||
v-model="form.recipient_address_detail"
|
||||
placeholder="请输入详细收货地址"
|
||||
autoHeight
|
||||
:maxlength="-1"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="popup-btn-group">
|
||||
<button class="btn-apple btn-secondary" @tap="showPopup=false">取消</button>
|
||||
<button class="btn-apple btn-secondary" @tap="handlePopupClose">取消</button>
|
||||
<button class="btn-apple btn-primary" @tap="submitExchange">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
|
||||
<up-picker
|
||||
:show="showAreaPicker"
|
||||
:columns="areaPickerColumns"
|
||||
keyName="label"
|
||||
title="选择所在地区"
|
||||
:defaultIndex="areaPickerDefaultIndex"
|
||||
closeOnClickOverlay
|
||||
@confirm="confirmAreaPicker"
|
||||
@change="handleAreaPickerChange"
|
||||
@cancel="showAreaPicker = false"
|
||||
@close="showAreaPicker = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { exchangeApi } from '@/api/index.js';
|
||||
import { useUserStore } from '@/store/index.js';
|
||||
import {
|
||||
buildRegionColumns,
|
||||
composeRecipientAddress,
|
||||
getRegionDisplayText,
|
||||
parseSavedRecipientAddress,
|
||||
parseShippingText
|
||||
} from '@/utils/shippingAddress.js';
|
||||
|
||||
const PHONE_REG = /^1[3-9]\d{9}$/;
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
let exchangeData = ref(null);
|
||||
let loading = ref(false);
|
||||
let showPopup = ref(false);
|
||||
let form = reactive({
|
||||
const exchangeData = ref(null);
|
||||
const loading = ref(false);
|
||||
const showPopup = ref(false);
|
||||
const showAreaPicker = ref(false);
|
||||
const areaPickerColumns = ref([]);
|
||||
const areaPickerDefaultIndex = ref([0, 0, 0]);
|
||||
const form = reactive({
|
||||
raw_shipping_text: '',
|
||||
recipient_name: '',
|
||||
recipient_phone: '',
|
||||
recipient_address: ''
|
||||
recipient_region_codes: [],
|
||||
recipient_address_detail: ''
|
||||
});
|
||||
|
||||
const regionDisplayText = computed(() => {
|
||||
return getRegionDisplayText(form.recipient_region_codes);
|
||||
});
|
||||
|
||||
const getStatusClass = (status) => {
|
||||
@@ -93,67 +168,166 @@
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (!time) return '-';
|
||||
return time.split('T').join(' ').slice(0, 19);
|
||||
return String(time).split('T').join(' ').slice(0, 19);
|
||||
};
|
||||
|
||||
const syncAreaPickerState = (regionCodes = []) => {
|
||||
const { columns, defaultIndex } = buildRegionColumns(regionCodes);
|
||||
areaPickerColumns.value = columns;
|
||||
areaPickerDefaultIndex.value = defaultIndex.map((index) => (index < 0 ? 0 : index));
|
||||
};
|
||||
|
||||
const loadExchange = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await exchangeApi.getPending(userStore.state.identifier);
|
||||
// 接口返回的是单个对象,不是数组
|
||||
exchangeData.value = data || null;
|
||||
} catch (e) {
|
||||
console.error('加载换货记录失败', e);
|
||||
} catch (error) {
|
||||
console.error('加载换货记录失败', error);
|
||||
exchangeData.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handlePopupClose = () => {
|
||||
showPopup.value = false;
|
||||
showAreaPicker.value = false;
|
||||
};
|
||||
|
||||
const openAreaPicker = () => {
|
||||
syncAreaPickerState(form.recipient_region_codes);
|
||||
showAreaPicker.value = true;
|
||||
};
|
||||
|
||||
const openPopup = () => {
|
||||
form.recipient_name = exchangeData?.recipient_name || '';
|
||||
form.recipient_phone = exchangeData?.recipient_phone || '';
|
||||
form.recipient_address = exchangeData?.recipient_address || '';
|
||||
const currentExchange = exchangeData.value || {};
|
||||
const { regionCodes, detailAddress } = parseSavedRecipientAddress(
|
||||
currentExchange.recipient_address || ''
|
||||
);
|
||||
|
||||
form.raw_shipping_text = '';
|
||||
form.recipient_name = currentExchange.recipient_name || '';
|
||||
form.recipient_phone = currentExchange.recipient_phone || '';
|
||||
form.recipient_region_codes = regionCodes;
|
||||
form.recipient_address_detail = detailAddress;
|
||||
|
||||
syncAreaPickerState(regionCodes);
|
||||
showPopup.value = true;
|
||||
};
|
||||
|
||||
const handleAreaPickerChange = (event) => {
|
||||
const selectedValues = event.value || [];
|
||||
const selectedProvinceCode = selectedValues[0]?.value || '';
|
||||
const selectedCityCode =
|
||||
event.columnIndex === 0 ? '' : selectedValues[1]?.value || '';
|
||||
const selectedDistrictCode =
|
||||
event.columnIndex === 2 ? selectedValues[2]?.value || '' : '';
|
||||
|
||||
const { columns, defaultIndex } = buildRegionColumns([
|
||||
selectedProvinceCode,
|
||||
selectedCityCode,
|
||||
selectedDistrictCode
|
||||
]);
|
||||
areaPickerColumns.value = columns;
|
||||
areaPickerDefaultIndex.value = defaultIndex.map((index) => (index < 0 ? 0 : index));
|
||||
};
|
||||
|
||||
const confirmAreaPicker = ({ value }) => {
|
||||
const selectedCodes = (value || []).map((item) => item?.value).filter(Boolean);
|
||||
if (selectedCodes.length === 3) {
|
||||
form.recipient_region_codes = selectedCodes;
|
||||
syncAreaPickerState(selectedCodes);
|
||||
}
|
||||
showAreaPicker.value = false;
|
||||
};
|
||||
|
||||
const fillFromPastedText = () => {
|
||||
if (!form.raw_shipping_text.trim()) {
|
||||
uni.showToast({ title: '请先粘贴收货信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parseShippingText(form.raw_shipping_text);
|
||||
let updatedCount = 0;
|
||||
|
||||
if (parsed.recipient_name) {
|
||||
form.recipient_name = parsed.recipient_name;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.recipient_phone) {
|
||||
form.recipient_phone = parsed.recipient_phone;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.regionCodes.length === 3) {
|
||||
form.recipient_region_codes = parsed.regionCodes;
|
||||
syncAreaPickerState(parsed.regionCodes);
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (parsed.detailAddress) {
|
||||
form.recipient_address_detail = parsed.detailAddress;
|
||||
updatedCount += 1;
|
||||
}
|
||||
|
||||
if (!updatedCount) {
|
||||
uni.showToast({ title: '未识别到有效收货信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showToast({ title: '已识别并填充', icon: 'success' });
|
||||
};
|
||||
|
||||
const submitExchange = async () => {
|
||||
if (!form.recipient_name) {
|
||||
if (!form.recipient_name.trim()) {
|
||||
uni.showToast({ title: '请输入收件人姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!form.recipient_phone) {
|
||||
|
||||
if (!form.recipient_phone.trim()) {
|
||||
uni.showToast({ title: '请输入收件人电话', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 手机号格式校验
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(form.recipient_phone)) {
|
||||
if (!PHONE_REG.test(form.recipient_phone.trim())) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.recipient_address) {
|
||||
uni.showToast({ title: '请输入收货地址', icon: 'none' });
|
||||
if (form.recipient_region_codes.length !== 3) {
|
||||
uni.showToast({ title: '请选择省市区', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.recipient_address_detail.trim()) {
|
||||
uni.showToast({ title: '请输入详细收货地址', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientAddress = composeRecipientAddress(
|
||||
form.recipient_region_codes,
|
||||
form.recipient_address_detail
|
||||
);
|
||||
|
||||
try {
|
||||
await exchangeApi.submitShippingInfo(
|
||||
exchangeData.value.id,
|
||||
form.recipient_name,
|
||||
form.recipient_phone,
|
||||
form.recipient_address
|
||||
form.recipient_name.trim(),
|
||||
form.recipient_phone.trim(),
|
||||
recipientAddress
|
||||
);
|
||||
uni.showToast({ title: '提交成功', icon: 'success' });
|
||||
showPopup.value = false;
|
||||
loadExchange();
|
||||
} catch (e) {
|
||||
console.error('提交换货信息失败', e);
|
||||
handlePopupClose();
|
||||
await loadExchange();
|
||||
} catch (error) {
|
||||
console.error('提交换货信息失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
syncAreaPickerState();
|
||||
loadExchange();
|
||||
});
|
||||
</script>
|
||||
@@ -167,24 +341,57 @@
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
min-height: 60vh;
|
||||
.empty-icon { font-size: 120rpx; margin-bottom: 30rpx; opacity: 0.6; }
|
||||
.empty-title { font-size: 32rpx; font-weight: 600; color: var(--text-primary); margin-bottom: 16rpx; }
|
||||
.empty-desc { font-size: 26rpx; color: var(--text-tertiary); text-align: center; margin-bottom: 40rpx; }
|
||||
.empty-btn { width: 100%; max-width: 400rpx; }
|
||||
|
||||
.empty-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.exchange-card {
|
||||
.exchange-header {
|
||||
margin-bottom: var(--space-md);
|
||||
.exchange-no { font-size: 28rpx; font-weight: 600; color: var(--text-primary); }
|
||||
|
||||
.exchange-no {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.exchange-info {
|
||||
.info-row {
|
||||
padding: var(--space-xs) 0;
|
||||
.info-label { font-size: 24rpx; color: var(--text-tertiary); }
|
||||
.info-value { font-size: 24rpx; color: var(--text-primary); }
|
||||
.address-value { max-width: 400rpx; text-align: right; }
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.address-value {
|
||||
max-width: 400rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,14 +399,21 @@
|
||||
margin-top: var(--space-lg);
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1rpx solid var(--gray-200);
|
||||
.btn-apple { width: 100%; }
|
||||
|
||||
.btn-apple {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 600rpx;
|
||||
width: 660rpx;
|
||||
max-height: 80vh;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
@@ -207,19 +421,55 @@
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: var(--space-md);
|
||||
|
||||
.form-label {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.form-helper {
|
||||
margin-top: var(--space-xs);
|
||||
font-size: 22rpx;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.parse-btn {
|
||||
width: 100%;
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.region-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 84rpx;
|
||||
padding: 0 24rpx;
|
||||
border: 2rpx solid var(--border-light);
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
|
||||
.region-trigger-text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-primary);
|
||||
|
||||
&.placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-btn-group {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
margin-top: var(--space-lg);
|
||||
.btn-apple { flex: 1; }
|
||||
|
||||
.btn-apple {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
packageName: '-',
|
||||
expireDate: '-',
|
||||
walletBalance: 0,
|
||||
iccid: '-',
|
||||
currentIccid: '-',
|
||||
category: '-',
|
||||
phone: '-',
|
||||
@@ -233,6 +234,7 @@
|
||||
deviceInfo.bound_phone = data.bound_phone || '';
|
||||
deviceInfo.packageName = data.current_package || '-';
|
||||
deviceInfo.expireDate = formatDate(data.current_package_expires_at);
|
||||
deviceInfo.iccid = data.iccid || '-';
|
||||
deviceInfo.walletBalance = data.wallet_balance ?? 0;
|
||||
isRealName.value = data.real_name_status === 1;
|
||||
realNameStatus.value = data.real_name_status === 1 ? '已实名' : '未实名';
|
||||
|
||||
Reference in New Issue
Block a user