fix: 新增账号注销功能

This commit is contained in:
若拙_233 2025-12-09 17:51:12 +08:00
parent 90c0f85972
commit 58f16be457
11 changed files with 599 additions and 105 deletions

View File

@ -5,6 +5,11 @@ export default {
getUserInfo: () => request.get('/base/userinfo'),
getUserMenu: () => request.get('/base/usermenu'),
getUserApi: () => request.get('/base/userapi'),
deleteAccount: (data) => request.delete('/app-user/account', {
data: {
code: data.code // Body 中携带 code
}
}),
// 手机号
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -52,6 +52,14 @@ export const basicRoutes = [
title: '抬头管理',
},
},
{
name: 'Logout',
path: 'logout',
component: () => import('@/views/user-center/components/Logout.vue'),
meta: {
title: '账号注销',
},
},
],
},
{

View File

@ -2,7 +2,17 @@
<div class="pages">
<AppHeader class="page-header" />
<!-- 页面初始化 loading -->
<div v-if="pageLoading" class="right" style="display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center">
<div
v-if="pageLoading"
class="right"
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
"
>
<n-spin size="large" />
<div style="font-size: 16px; color: #999999; margin-top: 20px">加载中...</div>
</div>
@ -305,7 +315,9 @@
:default-file-list="modalForm.inheritor_certificates"
:max="uploadLimit"
list-type="image-card"
:on-before-upload="(options) => handleBeforeUpload(options, 'inheritor_certificates')"
:on-before-upload="
(options) => handleBeforeUpload(options, 'inheritor_certificates')
"
@finish="handleFinish3"
@remove="delete3"
>
@ -569,8 +581,10 @@
</n-tooltip>
</div>
</template>
<div>
<div style="display: flex">
<n-select
style="width: 330px"
style="width: 220px"
v-model:value="modalForm.online_accounts[0]"
placeholder="请选择"
:options="accountsOptions"
@ -586,10 +600,13 @@
style="width: 220px; margin-left: 10px"
type="number"
/>
</div>
<div style="display: flex; margin-top: 12px">
<NInput
v-model:value="modalForm.online_accounts[3]"
placeholder="请输入评论数量"
style="width: 220px; margin-left: 10px"
style="width: 220px"
type="number"
/>
<NInput
@ -598,6 +615,14 @@
style="width: 220px; margin-left: 10px"
type="number"
/>
<NInput
v-model:value="modalForm.online_accounts[5]"
placeholder="请输入近七天点击量"
style="width: 220px; margin-left: 10px"
type="number"
/>
</div>
</div>
</n-form-item-gi>
</n-grid>
<n-grid v-if="currentStep == 4" :cols="24" :x-gap="0">
@ -903,7 +928,17 @@
重新评估
</div>
</div>
<div v-if="!pageLoading && status == 'pending'" class="right" style="display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center">
<div
v-if="!pageLoading && status == 'pending'"
class="right"
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
"
>
<img
class="loading-icon"
style="width: 100px; height: 100px"
@ -1168,7 +1203,7 @@ const modalRules = {
required: true,
message: '',
trigger: ['input', 'blur'],
len: 5,
len: 6,
fields: [
{
required: true,
@ -1195,6 +1230,11 @@ const modalRules = {
message: '请输入账号分享量',
trigger: ['input', 'blur'],
},
{
required: true,
message: '请输入近七天点击量',
trigger: ['input', 'blur'],
},
],
},
],
@ -1544,6 +1584,7 @@ const submit = () => {
likes: modalForm.online_accounts[2],
comments: modalForm.online_accounts[3],
shares: modalForm.online_accounts[4],
views: modalForm.online_accounts[5],
},
}
} else if (modalForm.online_accounts[0] == '1') {
@ -1553,6 +1594,7 @@ const submit = () => {
likes: modalForm.online_accounts[2],
comments: modalForm.online_accounts[3],
shares: modalForm.online_accounts[4],
views: modalForm.online_accounts[5],
},
}
} else if (modalForm.online_accounts[0] == '2') {
@ -1562,6 +1604,7 @@ const submit = () => {
likes: modalForm.online_accounts[2],
comments: modalForm.online_accounts[3],
shares: modalForm.online_accounts[4],
views: modalForm.online_accounts[5],
},
}
} else if (modalForm.online_accounts[0] == '3') {
@ -1571,6 +1614,7 @@ const submit = () => {
likes: modalForm.online_accounts[2],
comments: modalForm.online_accounts[3],
shares: modalForm.online_accounts[4],
views: modalForm.online_accounts[5],
},
}
}

View File

@ -185,11 +185,7 @@
</n-form-item>
<div class="btn-container">
<button
class="primary-btn"
:disabled="!isFormValid"
@click="handleUploadSubmit"
>
<button class="primary-btn" :disabled="!isFormValid" @click="handleUploadSubmit">
确认上传
</button>
</div>
@ -295,11 +291,7 @@ const message = useMessage()
//
const isFormValid = computed(() => {
return (
uploadedFiles.value.length > 0 &&
!!formModel.invoiceHeader &&
!!formModel.invoiceType
)
return uploadedFiles.value.length > 0 && !!formModel.invoiceHeader && !!formModel.invoiceType
})
const handleUploadFinish = ({ file, event }) => {
@ -384,10 +376,10 @@ defineExpose({
}
.title-bar {
width: 4px;
height: 16px;
width: 6px;
height: 24px;
background: #a30113;
border-radius: 2px;
border-radius: 4px;
}
.title-text {

View File

@ -315,10 +315,10 @@ const rowKey = (row) => row.id ?? row.name ?? row.company_name ?? row.taxId ?? r
}
.title-bar {
width: 4px;
height: 16px;
width: 6px;
height: 24px;
background: #a30113;
border-radius: 2px;
border-radius: 4px;
}
.title-text {

View File

@ -0,0 +1,368 @@
<template>
<div>
<div class="header-left">
<div class="title-bar"></div>
<div class="title-text">账号注销</div>
</div>
<div v-if="deleteAble" class="logout-container">
<!-- 手机号信息 -->
<div class="phone-info">当前绑定的手机号码为{{ maskedPhone }}</div>
<!-- 验证码输入区域 -->
<div class="code-form">
<n-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<n-form-item label="" prop="code" class="verify-code-item">
<!-- 验证码输入框 + 按钮 容器 -->
<div class="code-input-wrapper">
<n-input
v-model:value="form.code"
placeholder="手机验证码"
maxlength="6"
@input="handleCodeInput"
class="custom-code-input"
:bordered="false"
/>
<n-button
class="send-code-btn"
text
@click="handleSendCode"
:disabled="countDown > 0"
>
{{ countDown > 0 ? `${countDown}s后重新获取` : '获取验证码' }}
</n-button>
</div>
</n-form-item>
</n-form>
</div>
<!-- 注销按钮 -->
<n-button class="logout-btn" @click="handleLogout" :disabled="!isFormValid"> 确定 </n-button>
</div>
<div v-else class="logout-container" style="text-align: center">
<img src="@/assets/icon/评估完成.png" mode="scaleToFill" class="warn-icon" />
<p class="warn-title">无法注销</p>
<p class="warn-content">当前有未完成的估值记录/剩余估值次数无法注销账号</p>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onUnmounted } from 'vue'
import { defineProps, defineEmits } from 'vue'
import { useMessage, useDialog } from 'naive-ui' // Naive UI /
// Naive UI
const message = useMessage()
const dialog = useDialog()
//
const props = defineProps({
userPhone: {
type: String,
required: true,
},
deleteAble: {
type: Boolean,
required: true,
},
})
//
const emits = defineEmits(['send-code', 'confirm-logout'])
//
const formRef = ref(null)
//
const form = ref({
code: '',
})
//
const countDown = ref(0)
let timer = null
//
const maskedPhone = computed(() => {
if (!props.userPhone) return ''
return `${props.userPhone.slice(0, 3)}****${props.userPhone.slice(7, 11)}`
})
//
const rules = ref({
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 6, message: '请输入6位验证码', trigger: 'blur' },
],
})
//
const isFormValid = computed(() => {
return props.userPhone && form.value.code.length === 6
})
//
const handleCodeInput = () => {
if (form.value.code.length > 6) {
form.value.code = form.value.code.slice(0, 6)
}
}
//
const handleSendCode = async () => {
if (!props.userPhone) {
message.warning('手机号不存在,无法发送验证码')
return
}
//
const res = await emits('send-code', props.userPhone)
if (res) {
//
countDown.value = 60
timer = setInterval(() => {
countDown.value--
if (countDown.value <= 0) {
clearInterval(timer)
timer = null
}
}, 1000)
message.success('验证码发送成功')
}
}
//
const handleLogout = () => {
//
formRef.value.validate((errors) => {
if (!errors) {
emits('confirm-logout', {
code: form.value.code,
})
} else {
message.warning('请填写正确的6位验证码')
}
})
}
//
watch(
() => countDown.value,
(val) => {
if (val <= 0 && timer) {
clearInterval(timer)
timer = null
}
}
)
//
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
/* 根容器样式 */
.logout-container {
width: 600px;
margin: 50px auto;
padding: 30px;
background: #fff;
border-radius: 8px;
}
/* 标题样式 */
.logout-title {
font-size: 12px;
color: #303133;
line-height: 12px;
text-align: center;
}
/* 手机号信息样式 */
.phone-info {
font-size: 12px;
color: #303133;
}
/* 表单容器 */
.code-form {
margin-bottom: 40px;
margin-top: -20px;
}
/* 验证码表单项 */
.verify-code-item {
margin-bottom: 0 !important;
}
/* 验证码输入框 + 按钮 容器(核心布局) */
.code-input-wrapper {
display: flex;
align-items: center;
width: 100%;
height: 42px;
gap: 10px; /* 输入框和按钮间距 */
}
/* 自定义验证码输入框样式 */
.custom-code-input {
flex: 1;
height: 100%;
border-radius: 4px;
border: 1px solid #e5e5e5;
font-size: 14px;
transition: all 0.3s ease;
}
.warn-icon {
margin-top: 80px;
width: 100px;
height: 100px;
}
.warn-title {
margin-top: 20px;
font-size: 20px;
color: #000000;
font-weight: bold;
}
.warn-content {
margin-top: 10px;
font-size: 14px;
color: #999999;
}
/* 穿透修改 Naive UI 输入框样式 */
:deep(.custom-code-input .n-input-wrapper) {
height: 100%;
padding: 0 10px;
}
:deep(.custom-code-input .n-input__input-el) {
height: 100%;
line-height: 40px;
font-size: 14px;
}
/* 输入框 hover 状态 */
.custom-code-input:hover {
border-color: #a3011380; /* 半透明红 */
}
/* 输入框 聚焦状态 */
:deep(.custom-code-input .n-input-wrapper:focus-within) {
border-color: #a30113;
box-shadow: 0 0 0 2px rgba(163, 1, 19, 0.1);
outline: none;
}
/* 输入框 错误状态 */
:deep(.n-form-item--error .custom-code-input) {
border-color: #d03050;
}
:deep(.n-form-item--error .custom-code-input .n-input-wrapper:focus-within) {
border-color: #d03050;
box-shadow: 0 0 0 2px rgba(208, 48, 80, 0.1);
}
/* 验证码按钮样式 */
.send-code-btn {
height: 100%;
min-width: 120px;
padding: 0 15px;
border-radius: 4px;
color: #a30113;
font-size: 14px;
transition: all 0.3s ease;
white-space: nowrap;
background: #a3011332;
font-weight: bold;
}
/* 按钮 hover非禁用 */
.send-code-btn:not(:disabled):hover {
background: #a3011332;
color: #a30113;
}
/* 按钮 点击态 */
.send-code-btn:not(:disabled):active {
transform: scale(0.98);
}
/* 按钮 禁用态 */
.send-code-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* 注销按钮样式 */
.logout-btn {
margin: auto;
display: block;
width: 100%;
height: 48px;
font-size: 16px;
width: 180px;
height: 40px;
background: #a30113;
border-radius: 4px;
border: none;
border-radius: 4px;
color: #fff;
text-align: center;
}
/* 注销按钮 hover非禁用 */
.logout-btn:not(:disabled):hover {
background: #a30113;
color: #fff;
}
/* 注销按钮 禁用态 */
.logout-btn:disabled {
cursor: not-allowed;
opacity: 0.4;
}
.title-bar {
width: 6px;
height: 24px;
background: #a30113;
border-radius: 4px;
}
.title-text {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
/* 表单错误提示位置调整 */
:deep(.n-form-item-feedback-wrapper) {
position: absolute;
top: 100%;
left: 0;
padding-top: 2px;
line-height: 1;
}
:deep(.custom-code-input) {
/* Chrome/Safari/Edge */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
}
:deep(.n-button__content) {
justify-content: center;
}
</style>

View File

@ -6,6 +6,8 @@
</div>
<div class="user-phone">{{ userPhone || '18988880000' }}</div>
<div class="valuation-count">剩余评估次数{{ valuationCount }}</div>
<div class="logout-btn" @click="logout">退出登录</div>
</div>
<div class="menu-list">
@ -29,24 +31,48 @@
import iconHistory from '@/assets/icon/估值记录.png'
import iconTransfer from '@/assets/icon/对公转账.png'
import iconInvoice from '@/assets/icon/抬头管理.png'
import iconLogout from '@/assets/icon/iconLogout.png'
import { useDialog } from 'naive-ui'
import { useRouter } from 'vue-router'
const router = useRouter()
const dialog = useDialog()
defineProps({
userPhone: String,
valuationCount: Number,
currentMenu: String,
menuList: Array
menuList: Array,
})
defineEmits(['menu-click'])
function getMenuIcon(id) {
const iconMap = {
'history': iconHistory,
'transfer': iconTransfer,
'invoice': iconInvoice
history: iconHistory,
transfer: iconTransfer,
invoice: iconInvoice,
logout: iconLogout,
}
return iconMap[id]
}
async function logout() {
await new Promise((resolve, reject) => {
dialog.warning({
title: '提示',
content: '确认退出登录',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: () => resolve(),
onNegativeClick: () => reject('cancel'),
onClose: () => reject('cancel'),
})
})
//
localStorage.removeItem('ACCESS_TOKEN')
//
router.push('/login')
}
</script>
<style scoped>
@ -117,12 +143,12 @@ function getMenuIcon(id) {
}
.menu-item:hover {
background: #F5F7FA;
background: #f5f7fa;
}
.menu-item.active {
background: #FFF0F0;
color: #A30113;
background: #fff0f0;
color: #a30113;
font-weight: 500;
}
@ -153,6 +179,19 @@ function getMenuIcon(id) {
font-size: 14px;
}
.logout-btn {
width: 168px;
height: 32px;
background: #f5f5f5;
border-radius: 4px;
font-size: 14px;
color: #909399;
line-height: 32px;
text-align: center;
margin-top: 20px;
cursor: pointer;
}
@media (max-width: 768px) {
.sidebar-card {
width: 100%;

View File

@ -217,10 +217,10 @@ onMounted(fetchHistory)
}
.title-bar {
width: 4px;
height: 16px;
width: 6px;
height: 24px;
background: #a30113;
border-radius: 2px;
border-radius: 4px;
}
.title-text {

View File

@ -16,23 +16,19 @@
<!-- 右侧内容区 -->
<div class="content-area">
<!-- 返回按钮 (暂时注释) -->
<!-- <div class="back-button" @click="handleBackToHome">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>返回首页</span>
</div> -->
<!-- 子路由视图 -->
<router-view
:asset-list="assetList"
:invoice-list="invoiceList"
:user-phone="userPhone"
:deleteAble="deleteAble"
@return-home="handleBackToHome"
@add-invoice="addInvoice"
@update-invoice="updateInvoice"
@delete-invoice="deleteInvoice"
@upload-submit="uploadSubmit"
@send-code="sendVerificationCode"
@confirm-logout="confirmAccountLogout"
/>
</div>
</div>
@ -40,12 +36,17 @@
</template>
<script setup>
import { ref, onMounted, h, watch, computed } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useMessage, useDialog } from 'naive-ui' // Naive UI /
import api from '@/api'
import AppHeader from '@/components/AppHeader.vue'
import UserSidebar from './components/UserSidebar.vue'
// Naive UI
const message = useMessage()
const dialog = useDialog()
const router = useRouter()
const route = useRoute()
@ -55,12 +56,14 @@ const currentMenu = computed(() => {
if (path.includes('/history')) return 'history'
if (path.includes('/transfer')) return 'transfer'
if (path.includes('/invoice')) return 'invoice'
if (path.includes('/logout')) return 'logout' //
return 'history'
})
//
const userPhone = ref('')
const valuationCount = ref(0)
const deleteAble = ref(true)
//
const assetList = ref([])
@ -68,7 +71,7 @@ const assetList = ref([])
//
const invoiceList = ref([])
//
//
const menuList = ref([
{
id: 'history',
@ -82,6 +85,10 @@ const menuList = ref([
id: 'invoice',
label: '抬头管理',
},
{
id: 'logout', //
label: '账号注销',
},
])
//
@ -91,7 +98,6 @@ function handleBackToHome() {
//
function handleMenuClick(menu) {
// 使
router.push(`/user-center/${menu.id}`)
}
@ -114,10 +120,12 @@ async function loadData() {
}
} catch (error) {
console.error('加载估值记录失败:', error)
$message.error('加载估值记录失败,请稍后重试')
// ElMessage message
message.error('加载估值记录失败,请稍后重试')
}
}
//
//
async function loadInvoiceList() {
try {
const res = await api.getInvoiceHeaders()
@ -127,37 +135,43 @@ async function loadInvoiceList() {
}
}
//
//
async function addInvoice(data) {
try {
await api.addInvoiceHeaders(data)
$message.success('添加成功')
// ElMessage message
message.success('添加成功')
loadInvoiceList()
} catch (error) {
console.error('新增发票抬头失败:', error)
}
}
//
//
async function updateInvoice(data) {
try {
await api.updateInvoiceHeaders(data)
$message.success('编辑成功')
// ElMessage message
message.success('编辑成功')
loadInvoiceList()
} catch (error) {
console.error('更新发票抬头失败:', error)
}
}
//
//
async function deleteInvoice(data) {
try {
await api.deleteInvoiceHeaders(data.id)
$message.success('删除成功')
// ElMessage message
message.success('删除成功')
loadInvoiceList()
} catch (error) {
console.error('删除发票抬头失败:', error)
}
}
//
//
async function uploadSubmit(data) {
try {
console.log('上传对公转账凭证===', data)
@ -167,6 +181,53 @@ async function uploadSubmit(data) {
}
}
//
async function sendVerificationCode(phone) {
try {
await api.sendVerifyCode({ phone })
message.success('验证码已发送,请注意查收')
return true
} catch (error) {
console.error('发送验证码失败:', error)
return false
}
}
//
async function confirmAccountLogout(formData) {
// ElMessageBox.confirm dialog.warning
await new Promise((resolve, reject) => {
dialog.warning({
title: '提示',
content: '提交后账号将注销,不可撤回,确认注销账户?',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: () => resolve(),
onNegativeClick: () => reject('cancel'),
onClose: () => reject('cancel'),
})
})
//
api
.deleteAccount({
code: formData.code,
})
.then((res) => {
console.log(res, '111111111111111')
// ElMessage message
message.success('账号注销成功')
//
localStorage.removeItem('phone')
//
router.push('/login')
})
.catch((err) => {
console.log(err, '111111111111111')
deleteAble.value = false
})
}
onMounted(() => {
loadData()
loadInvoiceList()
@ -200,26 +261,7 @@ onMounted(() => {
min-height: calc(100vh - 100px); /* Adjust based on header + margin */
position: relative;
overflow: hidden; /* Ensure rounded corners */
}
.back-button {
position: absolute;
top: 20px;
left: 20px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0;
color: #606266;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
background: transparent;
z-index: 10;
}
.back-button:hover {
color: #a30113;
padding: 20px; /* 新增:添加内边距 */
}
@media (max-width: 1200px) {
@ -232,9 +274,5 @@ onMounted(() => {
.main-container {
flex-direction: column;
}
.header {
padding: 10px 20px;
}
}
</style>