仿照支付宝实现注销流程

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;
  }
};
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容