Files
device-voice-h5/components/PopupCandidate.vue
2026-09-16 16:32:08 +08:00

297 lines
7.6 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 v-if='visible' class='popup-candidate' @tap.stop>
<view class='popup-candidate-card'>
<template v-if='isCandidateStep'>
<view class='popup-header'>
<view class='popup-heading'>{{ candidate?.title || '提示' }}</view>
<view class='popup-close' role='button' aria-label='关闭' @tap='dismiss'>×</view>
</view>
<scroll-view scroll-y class='popup-body'>
<text>{{ candidate?.body || '' }}</text>
</scroll-view>
<view class='popup-actions'>
<up-button v-if='isRiskExchange' type='primary' @tap='goToAddress'>申请换卡</up-button>
<up-button v-else-if='actionLabel' type='primary' @tap='handleAction'>{{ actionLabel }}</up-button>
<up-button plain @tap='dismiss'>稍后处理</up-button>
</view>
</template>
<template v-else-if='isAddressStep'>
<view class='popup-header'>
<view class='popup-heading'>换卡收货信息</view>
<view class='popup-close' role='button' aria-label='关闭' @tap='dismiss'>×</view>
</view>
<view class='form-item'>
<view class='form-label'>收件人姓名</view>
<up-input v-model='addressForm.recipient_name' placeholder='请输入收件人姓名' border='surround' maxlength='50'/>
</view>
<view class='form-item'>
<view class='form-label'>收件人电话</view>
<up-input v-model='addressForm.recipient_phone' type='number' placeholder='请输入收件人电话' border='surround' maxlength='20'/>
</view>
<view class='form-item'>
<view class='form-label'>收货地址</view>
<up-textarea v-model='addressForm.recipient_address' placeholder='请输入完整收货地址'/>
</view>
<view class='popup-actions'>
<up-button type='primary' :loading='submitting' :disabled='submitting' @tap='submitAddress'>提交</up-button>
</view>
</template>
<template v-else-if='isDoneStep'>
<view class='popup-header'>
<view class='popup-heading'>申请已提交</view>
</view>
<view class='result-item'>
<view class='result-label'>换货单号</view>
<view class='result-value'>{{ exchange?.exchange_no || '-' }}</view>
</view>
<view class='result-item'>
<view class='result-label'>状态</view>
<view class='result-value'>{{ exchange?.status_name || '-' }}</view>
</view>
<view v-if='exchange?.recipient_address' class='result-item'>
<view class='result-label'>收货地址</view>
<view class='result-value'>{{ exchange.recipient_address }}</view>
</view>
<view class='popup-actions'>
<up-button type='primary' @tap='finish'>完成</up-button>
</view>
</template>
</view>
</view>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import { popupApi, notificationApi } from '@/api/index.js';
import { handledPopupNotificationIds } from '@/utils/popup.js';
const props = defineProps({
page: {
type: String,
required: true
},
identifier: {
type: String,
default: ''
}
});
const emit = defineEmits(['close']);
const visible = ref(false);
const step = ref('candidate');
const candidate = ref(null);
const exchange = ref(null);
const submitting = ref(false);
const addressForm = reactive({
recipient_name: '',
recipient_phone: '',
recipient_address: ''
});
const isCandidateStep = computed(() => step.value === 'candidate');
const isAddressStep = computed(() => step.value === 'address');
const isDoneStep = computed(() => step.value === 'done');
const isRiskExchange = computed(() => candidate.value?.popup_type === 'risk_exchange');
const actionLabelMap = {
package_purchase: '立即购买',
asset_wallet_recharge: '立即充值'
};
const actionRouteMap = {
package_purchase: '/pages/package-order/package-order',
asset_wallet_recharge: '/pages/my-wallet/my-wallet'
};
const actionLabel = computed(() => actionLabelMap[candidate.value?.action_type] || '');
const goToAddress = () => {
step.value = 'address';
};
const resetForm = () => {
addressForm.recipient_name = '';
addressForm.recipient_phone = '';
addressForm.recipient_address = '';
};
const markPopupRead = async () => {
const id = candidate.value?.notification_id;
if (!id) return;
try {
await notificationApi.markRead(id);
} catch (e) {
console.error('标记弹窗通知已读失败', e);
}
};
const dismiss = () => {
visible.value = false;
markPopupRead();
emit('close', 'dismissed');
};
const handleAction = () => {
const url = actionRouteMap[candidate.value?.action_type];
visible.value = false;
markPopupRead();
emit('close', 'action');
if (url) {
uni.navigateTo({ url });
}
};
const submitAddress = async () => {
if (submitting.value) return;
const { recipient_name, recipient_phone, recipient_address } = addressForm;
if (!recipient_name || !recipient_phone || !recipient_address) {
uni.showToast({ title: '请填写完整收货信息', icon: 'none' });
return;
}
const assetId = Number(candidate.value?.asset_id);
if (!Number.isInteger(assetId) || assetId <= 0) {
uni.showToast({ title: '资产信息无效', icon: 'none' });
return;
}
submitting.value = true;
try {
exchange.value = await popupApi.submitRiskExchangeAddress(assetId, {
recipient_name,
recipient_phone,
recipient_address
});
await markPopupRead();
step.value = 'done';
} catch (e) {
console.error('提交风险换卡收货地址失败', e);
} finally {
submitting.value = false;
}
};
const finish = () => {
visible.value = false;
emit('close', 'done');
};
onMounted(async () => {
if (!props.identifier) {
emit('close', 'empty');
return;
}
try {
const data = await popupApi.getCandidate(props.page, props.identifier);
const item = (data && data.candidate) || null;
if (!item || !item.notification_id || handledPopupNotificationIds.has(item.notification_id)) {
emit('close', 'empty');
return;
}
handledPopupNotificationIds.add(item.notification_id);
candidate.value = item;
step.value = 'candidate';
resetForm();
visible.value = true;
} catch (e) {
// 资源不可见400/code=1180与接口异常统一静默关闭不做 UI 分支
console.error('查询弹窗候选失败', e);
emit('close', 'invisible');
}
});
</script>
<style scoped lang='scss'>
.popup-candidate {
position: fixed;
z-index: 1100;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
background: rgba(0, 0, 0, 0.48);
}
.popup-candidate-card {
width: 100%;
max-width: 640rpx;
overflow: hidden;
border-radius: 24rpx;
background: #fff;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.18);
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 32rpx 18rpx;
}
.popup-heading {
color: var(--text-primary);
font-size: 34rpx;
font-weight: 600;
}
.popup-close {
width: 52rpx;
height: 52rpx;
color: var(--text-tertiary);
font-size: 48rpx;
font-weight: 300;
line-height: 46rpx;
text-align: center;
}
.popup-body {
box-sizing: border-box;
max-height: 420rpx;
padding: 12rpx 32rpx 24rpx;
color: var(--text-secondary);
font-size: 28rpx;
line-height: 1.7;
white-space: pre-wrap;
}
.popup-actions {
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 20rpx 32rpx 32rpx;
}
.form-item {
padding: 0 32rpx 20rpx;
}
.form-label {
margin-bottom: 12rpx;
color: var(--text-primary);
font-size: 26rpx;
}
.result-item {
padding: 8rpx 32rpx;
}
.result-label {
color: var(--text-tertiary);
font-size: 24rpx;
}
.result-value {
margin-top: 4rpx;
color: var(--text-primary);
font-size: 28rpx;
word-break: break-all;
}
</style>