300 lines
6.7 KiB
Vue
300 lines
6.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { AppButton, SmsCodeInput } from '@/components'
|
|
|
|
export type BindContactMode =
|
|
| 'phone-bind'
|
|
| 'phone-rebind'
|
|
| 'email-bind'
|
|
| 'email-rebind'
|
|
| 'identity'
|
|
|
|
interface Props {
|
|
mode: BindContactMode
|
|
hintPhone?: string
|
|
hintEmail?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
hintPhone: '',
|
|
hintEmail: '',
|
|
})
|
|
|
|
const visible = defineModel<boolean>('visible', { default: false })
|
|
|
|
const emit = defineEmits<{ submit: [payload: { contact: string; code: string }] }>()
|
|
|
|
const contact = ref('')
|
|
const code = ref('')
|
|
const agree = ref(false)
|
|
|
|
const isEmailMode = computed(() => props.mode === 'email-bind' || props.mode === 'email-rebind')
|
|
const isIdentity = computed(() => props.mode === 'identity')
|
|
|
|
const title = computed<string>(() => {
|
|
const map: Record<BindContactMode, string> = {
|
|
'phone-bind': '绑定手机号',
|
|
'phone-rebind': '验证并换绑',
|
|
'email-bind': '绑定邮箱',
|
|
'email-rebind': '更换绑定邮箱',
|
|
identity: '安全身份验证',
|
|
}
|
|
return map[props.mode]
|
|
})
|
|
|
|
const fieldLabel = computed<string>(() => {
|
|
const map: Record<BindContactMode, string> = {
|
|
'phone-bind': '手机号',
|
|
'phone-rebind': '新手机号',
|
|
'email-bind': '邮箱',
|
|
'email-rebind': '新邮箱',
|
|
identity: '手机号',
|
|
}
|
|
return map[props.mode]
|
|
})
|
|
|
|
const submitText = computed<string>(() => {
|
|
const map: Record<BindContactMode, string> = {
|
|
'phone-bind': '验证并绑定',
|
|
'phone-rebind': '验证并换绑',
|
|
'email-bind': '确认修改',
|
|
'email-rebind': '确认修改',
|
|
identity: '验证',
|
|
}
|
|
return map[props.mode]
|
|
})
|
|
|
|
const contactPlaceholder = computed<string>(() => {
|
|
if (isEmailMode.value) return props.hintEmail || '请输入邮箱'
|
|
return props.mode === 'phone-rebind' ? '请输入新手机号' : '请输入手机号'
|
|
})
|
|
|
|
const identityDesc = computed(() =>
|
|
isIdentity.value
|
|
? `安全身份验证:为确保账号安全,请完成身份验证后再进行账号操作,发送验证码至 ${props.hintPhone}`
|
|
: '',
|
|
)
|
|
|
|
watch(visible, (open) => {
|
|
if (!open) return
|
|
contact.value = isIdentity.value ? props.hintPhone : ''
|
|
code.value = ''
|
|
agree.value = false
|
|
})
|
|
|
|
function onGetCode(): void {
|
|
ElMessage.success('验证码已发送')
|
|
}
|
|
|
|
function onSubmit(): void {
|
|
if (!contact.value.trim()) {
|
|
ElMessage.warning(isEmailMode.value ? '请输入邮箱' : '请输入手机号')
|
|
return
|
|
}
|
|
if (!code.value) {
|
|
ElMessage.warning('请输入验证码')
|
|
return
|
|
}
|
|
if (!isIdentity.value && !agree.value) {
|
|
ElMessage.warning('请先阅读并同意《用户协议》和《隐私政策》')
|
|
return
|
|
}
|
|
emit('submit', { contact: contact.value.trim(), code: code.value })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<div v-if="visible" class="bind-dialog" @click.self="visible = false">
|
|
<div class="bind-dialog__panel">
|
|
<h3 class="bind-dialog__title">
|
|
{{ title }}
|
|
</h3>
|
|
<p v-if="identityDesc" class="bind-dialog__desc text-secondary">
|
|
{{ identityDesc }}
|
|
</p>
|
|
|
|
<div class="bind-dialog__field">
|
|
<label class="bind-dialog__label">{{ fieldLabel }}</label>
|
|
<input
|
|
v-model="contact"
|
|
class="bind-dialog__input"
|
|
type="text"
|
|
:placeholder="contactPlaceholder"
|
|
>
|
|
</div>
|
|
|
|
<div class="bind-dialog__field">
|
|
<label class="bind-dialog__label">验证码</label>
|
|
<SmsCodeInput v-model:code="code" @get-code="onGetCode" />
|
|
</div>
|
|
|
|
<label v-if="!isIdentity" class="bind-dialog__agree">
|
|
<input v-model="agree" type="checkbox">
|
|
<span>我已阅读并同意<em>《用户协议》</em>和<em>《隐私政策》</em></span>
|
|
</label>
|
|
|
|
<div class="bind-dialog__footer">
|
|
<AppButton type="default" @click="visible = false">
|
|
取消
|
|
</AppButton>
|
|
<AppButton type="primary" @click="onSubmit">
|
|
{{ submitText }}
|
|
</AppButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.bind-dialog {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 1000;
|
|
.flex(row, center);
|
|
background: transparent;
|
|
overflow-y: auto;
|
|
|
|
&__panel {
|
|
width: 700px;
|
|
max-width: calc(100vw - 32px);
|
|
max-height: calc(100vh - 48px);
|
|
overflow-y: auto;
|
|
padding: @spacing-base;
|
|
border-radius: @radius-lg;
|
|
background: @white;
|
|
box-shadow: @shadow-lg;
|
|
.flex(column, flex-start);
|
|
gap: @spacing-base;
|
|
.scrollbar();
|
|
}
|
|
|
|
&__title {
|
|
position: relative;
|
|
padding-left: 12px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
color: @heading-color;
|
|
line-height: 20px;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 3px;
|
|
width: 3px;
|
|
height: 14px;
|
|
border-radius: 2px;
|
|
background: @primary-strong;
|
|
}
|
|
}
|
|
|
|
&__title:has(+ .bind-dialog__desc) {
|
|
padding-left: 0;
|
|
font-size: 11px;
|
|
color: @primary-strong;
|
|
|
|
&::before {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
&__desc {
|
|
padding: 0;
|
|
background: transparent;
|
|
color: @text-color-secondary;
|
|
font-size: 11px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
&__field {
|
|
.flex(column, flex-start);
|
|
gap: @spacing-xs;
|
|
width: 100%;
|
|
}
|
|
|
|
&__label {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: @heading-color;
|
|
}
|
|
|
|
&__input {
|
|
width: 100%;
|
|
height: 32px;
|
|
padding: 0 12px;
|
|
border: 1px solid @profile-input-border;
|
|
border-radius: @profile-radius;
|
|
background: @white;
|
|
font-size: 11px;
|
|
color: @text-color;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
box-sizing: border-box;
|
|
|
|
&:focus {
|
|
border-color: @primary-strong;
|
|
}
|
|
|
|
&::placeholder {
|
|
color: @heading-color;
|
|
}
|
|
}
|
|
|
|
&__agree {
|
|
.flex(row, flex-start, center);
|
|
gap: @spacing-sm;
|
|
font-size: 11px;
|
|
color: @text-color-secondary;
|
|
cursor: pointer;
|
|
|
|
input {
|
|
accent-color: @primary-strong;
|
|
}
|
|
|
|
em {
|
|
font-style: normal;
|
|
color: @primary-strong;
|
|
}
|
|
}
|
|
|
|
&__footer {
|
|
.flex(row, flex-start);
|
|
gap: @spacing-sm;
|
|
width: 100%;
|
|
}
|
|
|
|
.app-button {
|
|
flex: 1;
|
|
height: 36px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
border-radius: @profile-radius;
|
|
}
|
|
|
|
.app-button--primary {
|
|
border-color: @primary-strong;
|
|
background: @primary-strong;
|
|
|
|
&:hover {
|
|
border-color: darken(@primary-strong, 6%);
|
|
background: darken(@primary-strong, 6%);
|
|
}
|
|
}
|
|
|
|
.app-button--default {
|
|
border: none;
|
|
background: @profile-quiet-btn;
|
|
color: @text-color;
|
|
|
|
&:hover {
|
|
border: none;
|
|
background: darken(@profile-quiet-btn, 4%);
|
|
color: @text-color;
|
|
}
|
|
}
|
|
}
|
|
</style>
|