image.png
使用Uniapp + Vue3 + TypeScript实现类似支付宝账号注销确认界面:
新建ReasonSelect.vue文件
<template>
<view class="container">
<view class="title">可以告诉我们原因吗?</view>
<view class="options-container">
<view v-for="option in options" :key="option.value" class="option-item" @click="selectOption(option.value)">
<view class="option-text">{{ option.label }}</view>
<view class="radio-container">
<view class="radio" :class="{ 'radio-selected': selectedOption === option.value }">
<view v-if="selectedOption === option.value" class="radio-check"></view>
</view>
</view>
</view>
<view v-if="selectedOption === 'other'" class="input-container">
<textarea v-model="otherReason" class="reason-input" placeholder="请输入详细原因" maxlength="300"
@input="handleInput"></textarea>
<view class="char-counter">{{ inputCount }}/300</view>
</view>
</view>
<view v-if="showDialog" class="dialog-mask">
<view class="dialog-container">
<view class="dialog-content">
若你换了新手机号,更新当前账号的手机号后,即可继续使用
</view>
<view class="dialog-buttons">
<view class="dialog-button" @click="cancelChangePhone">取消</view>
<view class="dialog-button primary" @click="confirmChangePhone">更换手机号</view>
</view>
</view>
</view>
<view class="footer">
<button class="next-button" @click="handleNext">下一步</button>
<view class="skip-text" @click="handleSkip">跳过</view>
</view>
</view>
</template>
<script lang="ts">
import { ref, computed } from 'vue'
export default {
setup() {
const options = ref([
{ label: '没有使用诉求/注销多余账号', value: 'no_need' },
{ label: '换了新手机号', value: 'new_phone' },
{ label: '担心账号被盗', value: 'security' },
{ label: '其他', value: 'other' }
])
const selectedOption = ref<string | null>(null)
const otherReason = ref('')
const inputCount = ref(0)
const showDialog = ref(false)
const selectOption = (value : string) => {
selectedOption.value = value
// 如果选择了"换了新手机号",显示弹窗
if (value === 'new_phone') {
showDialog.value = true
}
}
const handleInput = (e : any) => {
inputCount.value = e.detail.value.length
}
const cancelChangePhone = () => {
showDialog.value = false
selectedOption.value = null
}
const confirmChangePhone = () => {
showDialog.value = false
// 这里可以添加跳转到更换手机号页面的逻辑
uni.navigateTo({
url: '/pages/account/changePhone'
})
}
const handleNext = () => {
if (!selectedOption.value) {
uni.showToast({
title: '请选择注销原因',
icon: 'none'
})
return
}
if (selectedOption.value === 'other' && !otherReason.value.trim()) {
uni.showToast({
title: '请输入详细原因',
icon: 'none'
})
return
}
// 提交数据逻辑
const reason = selectedOption.value === 'other'
? otherReason.value
: options.value.find(o => o.value === selectedOption.value)?.label
console.log('选择的注销原因:', reason)
// 跳转到下一步
uni.navigateTo({
url: '/pages/logout/logout'
})
}
const handleSkip = () => {
// 跳过逻辑
uni.navigateTo({
url: '/pages/logout/logout'
})
}
return {
options,
selectedOption,
otherReason,
inputCount,
showDialog,
selectOption,
handleInput,
cancelChangePhone,
confirmChangePhone,
handleNext,
handleSkip
}
}
}
</script>
<style lang="scss">
.container {
padding: 24rpx;
background-color: #f5f5f5;
min-height: 100vh;
box-sizing: border-box;
max-width: 100%;
overflow-x: hidden;
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin: 24rpx 0 40rpx;
color: #333;
word-break: break-word;
}
.options-container {
background-color: #fff;
border-radius: 16rpx;
padding: 0 24rpx;
margin: 0 auto;
max-width: 100%;
box-sizing: border-box;
}
.option-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 28rpx 0;
border-bottom: 1rpx solid #eee;
min-height: 96rpx;
box-sizing: border-box;
&:last-child {
border-bottom: none;
}
}
.option-text {
font-size: 30rpx;
color: #333;
flex: 1;
margin-right: 20rpx;
word-break: break-word;
}
.radio-container {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.radio {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.radio-selected {
border-color: #007AFF;
background-color: #007AFF;
}
.radio-check {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background-color: #fff;
}
.input-container {
margin-top: 20rpx;
position: relative;
padding-bottom: 20rpx;
}
.reason-input {
width: 100%;
height: 180rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.char-counter {
text-align: right;
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
padding: 0 24rpx;
box-sizing: border-box;
}
.dialog-container {
width: 100%;
max-width: 600rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.dialog-content {
padding: 40rpx;
font-size: 30rpx;
color: #333;
text-align: center;
word-break: break-word;
}
.dialog-buttons {
display: flex;
border-top: 1rpx solid #eee;
}
.dialog-button {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 30rpx;
color: #007AFF;
&.primary {
border-left: 1rpx solid #eee;
}
}
.footer {
margin-top: 60rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
box-sizing: border-box;
width: 100%;
}
.next-button {
width: 100%;
max-width: 600rpx;
background-color: #007AFF;
color: #fff;
border-radius: 48rpx;
font-size: 32rpx;
height: 88rpx;
line-height: 88rpx;
border: none;
}
.skip-text {
margin-top: 24rpx;
font-size: 28rpx;
color: #999;
text-decoration: underline;
}
</style>
创建注销确认页面AccountCancel.vue
<template>
<view class="account-cancel-container">
<!-- 顶部标题 -->
<view class="header">
<text class="header-title">请确认注销的账号及影响</text>
</view>
<!-- 注销提示 -->
<view class="warning-text">
<text>注销后,永久不可登录和使用该账号</text>
</view>
<!-- 账号信息 -->
<view class="account-info">
<view class="info-item">
<text>{{ maskedPhone }}</text>
</view>
<view class="info-item">
<text>昵称:{{ nickname }}</text>
</view>
<view class="info-item">
<text>姓名:{{ maskedName }}</text>
</view>
</view>
<!-- 注销影响列表 -->
<view class="impact-list">
<text class="impact-title">账号内信息和服务将全部清空或关闭</text>
<view class="impact-item" v-for="(item, index) in impacts" :key="index">
<text>· {{ item }}</text>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="action-buttons">
<button class="confirm-btn" @click="handleConfirmCancel">我已确认,继续注销</button>
<!-- <view class="other-account-link" @click="handleCancelOtherAccount">
<text>注销本人其他账号</text>
</view> -->
</view>
</view>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { DeleteCompanyAccount } from '@/api/todo/index'
import { useConfigStore } from '@/store/config'
export default defineComponent({
name: 'AccountCancel',
setup() {
// 账号信息
const phone = '15912345604';
const maskedPhone = ref(phone.replace(/(\d{3})\d{4}(\d{2})/, '$1****$2'));
const nickname = ref('尊');
const name = ref('张尊');
const maskedName = ref(`*${name.value.slice(-1)}`);
// 注销影响列表
const impacts = ref([
'身份、账号和交易信息将清空',
'卡券、积分和会员权益将清空',
'已开通的服务(如花呗、芝麻信用等)将关闭',
'已签约的商家服务将失效',
'与其他账号绑定关系(如淘宝、1688等)将解除',
'终止已开通的理财定投协议'
]);
// 确认注销
const handleConfirmCancel = () => {
uni.showModal({
title: '确认注销',
content: '确定要注销当前账号吗?此操作不可撤销。',
success: (res) => {
if (res.confirm) {
// 调用注销API
cancelAccount();
}
}
});
};
// 注销其他账号
const handleCancelOtherAccount = () => {
uni.navigateTo({
url: '/pages/account/cancelOther'
});
};
const configStore = useConfigStore();
// 注销账号API调用
const cancelAccount = async () => {
try {
uni.showLoading({
title: '正在注销...'
});
// 这部分是真实注销API调用,下面是模拟效果
// const res = await cancelAccountApi();
// DeleteCompanyAccount({
// CompanyId: configStore.companyBaseId,
// }).then((res) => {
// if (res.Data) {
// configStore.updatedbId('')
// configStore.updatecompanyBaseId('')
// configStore.updatekeyCode('')
// configStore.updatecurServiceIp('')
// configStore.updatecurrentServiceUrl('')
// configStore.updatecurrentServiceUrlWeb('')
// uni.hideLoading();
// uni.showToast({
// title: '账号已注销',
// icon: 'success'
// });
// // 注销成功后跳转到登录页或其他页面
// setTimeout(() => {
// uni.reLaunch({
// url: '/pages/login/index'
// });
// }, 1500);
// }
// })
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '账号已注销',
icon: 'success'
});
// 注销成功后跳转到登录页或其他页面
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
});
}, 1500);
}, 2000);
} catch (error) {
uni.hideLoading();
uni.showToast({
title: '注销失败',
icon: 'error'
});
}
};
return {
maskedPhone,
nickname,
maskedName,
impacts,
handleConfirmCancel,
handleCancelOtherAccount
};
}
});
</script>
<style lang="scss" scoped>
.account-cancel-container {
padding: 20rpx 30rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
margin-bottom: 40rpx;
text-align: center;
.header-title {
font-size: 36rpx;
font-weight: bold;
color: #000;
}
}
.warning-text {
margin-bottom: 30rpx;
color: #f56c6c;
font-size: 28rpx;
}
.account-info {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 30rpx;
.info-item {
padding: 15rpx 0;
font-size: 28rpx;
color: #333;
}
}
.impact-list {
background-color: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 40rpx;
.impact-title {
display: block;
margin-bottom: 20rpx;
font-size: 28rpx;
color: #333;
}
.impact-item {
padding: 10rpx 0;
font-size: 26rpx;
color: #666;
}
}
.action-buttons {
.confirm-btn {
background-color: #1677ff;
color: #fff;
border-radius: 50rpx;
font-size: 30rpx;
height: 90rpx;
line-height: 90rpx;
margin-bottom: 30rpx;
}
.other-account-link {
text-align: center;
color: #1677ff;
font-size: 28rpx;
}
}
</style>
调用注销API
// services/account.ts
import { request } from '@/utils/request';
export const cancelAccount = async (params: {
userId: string;
verificationCode?: string;
}): Promise<boolean> => {
try {
const res = await request({
url: '/api/account/cancel',
method: 'POST',
data: params
});
return res.success;
} catch (error) {
throw error;
}
};