主要变更: 1. 重构API目录结构,采用模块化设计,适配5.x后端接口规范 2. 优化布局组件,提升用户界面体验 3. 增强国际化支持,完善多语言配置 4. 新增权限控制功能,支持前端路由权限管理 5. 优化认证流程,支持JWT认证机制 6. 新增中间件和插件,提升系统扩展性 7. 优化状态管理,采用更清晰的模块化设计 8. 新增搜索框、广告等组件,丰富页面功能 9. 更改包管理工具从npm到pnpm,提升依赖管理效率 10. 完善配置系统,支持多环境配置 适配5.x后端特性: - 采用新的API接口规范 - 支持JWT认证机制 - 适配后端权限控制体系 - 支持后端国际化接口 - 优化数据交互方式
280 lines
6.3 KiB
Vue
280 lines
6.3 KiB
Vue
<template>
|
|
<div class="sms-code-container">
|
|
<el-button
|
|
type="primary"
|
|
link
|
|
:disabled="!canSendCode"
|
|
@click="handleSendCode"
|
|
class="sms-code-button"
|
|
>
|
|
{{ buttonText }}
|
|
</el-button>
|
|
</div>
|
|
|
|
<el-dialog
|
|
v-model="showCaptchaDialog"
|
|
:title="t('captcha_title')"
|
|
width="350px"
|
|
:append-to-body="true"
|
|
:align-center="true"
|
|
class="captcha-dialog"
|
|
>
|
|
<el-form
|
|
:model="captchaForm"
|
|
ref="captchaFormRef"
|
|
:rules="captchaRules"
|
|
@submit.native.prevent
|
|
class="captcha-form"
|
|
>
|
|
<el-form-item prop="captcha_code" class="captcha-form-item">
|
|
<el-input
|
|
v-model="captchaForm.captcha_code"
|
|
:placeholder="t('captcha_placeholder')"
|
|
class="captcha-input"
|
|
>
|
|
<template #suffix>
|
|
<div class="captcha-image-wrapper">
|
|
<el-image
|
|
:src="captchaImage"
|
|
class="captcha-image"
|
|
@click="refreshCaptcha"
|
|
></el-image>
|
|
</div>
|
|
</template>
|
|
</el-input>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="closeCaptchaDialog" class="cancel-button">
|
|
{{ t('cancel') }}
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
:loading="isLoading"
|
|
@click="confirmSendCode"
|
|
class="confirm-button"
|
|
>
|
|
{{ t('confirm') }}
|
|
</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, reactive, computed, watch } from 'vue'
|
|
import { sendSmsVerificationCode } from '@/api/system'
|
|
import type { FormInstance } from 'element-plus'
|
|
|
|
interface SmsCodeProps {
|
|
mobile?: string
|
|
email?: string
|
|
type: string
|
|
modelValue: string
|
|
}
|
|
|
|
interface CaptchaFormData {
|
|
mobile?: string
|
|
email?: string
|
|
captcha_code: string
|
|
captcha_key: string
|
|
type: string
|
|
}
|
|
|
|
const props = defineProps<SmsCodeProps>()
|
|
const emit = defineEmits(['update:modelValue', 'click'])
|
|
|
|
// 响应式数据
|
|
const isLoading = ref(false)
|
|
const showCaptchaDialog = ref(false)
|
|
const canSendCode = ref(true)
|
|
const buttonText = ref('')
|
|
const seconds = ref(90)
|
|
let countdownTimer: number | undefined = undefined
|
|
|
|
// 验证码相关
|
|
const captchaForm = reactive<CaptchaFormData>({
|
|
mobile: props.mobile || '',
|
|
email: props.email || '',
|
|
captcha_code: '',
|
|
captcha_key: '',
|
|
type: props.type
|
|
})
|
|
|
|
// 监听 props 变化,更新 captchaForm
|
|
watch(() => props.mobile, (newVal) => {
|
|
captchaForm.mobile = newVal || ''
|
|
})
|
|
watch(() => props.email, (newVal) => {
|
|
captchaForm.email = newVal || ''
|
|
})
|
|
|
|
const captchaImage = ref('')
|
|
const captchaFormRef = ref<FormInstance>()
|
|
|
|
// 验证规则
|
|
const captchaRules = reactive({
|
|
captcha_code: {
|
|
required: true,
|
|
message: '请输入验证码',
|
|
trigger: ['blur', 'change']
|
|
}
|
|
})
|
|
|
|
// 计算属性
|
|
const smsValue = computed({
|
|
get() {
|
|
return props.modelValue
|
|
},
|
|
set(value) {
|
|
emit('update:modelValue', value)
|
|
}
|
|
})
|
|
|
|
// 初始化按钮文本
|
|
buttonText.value = t('get_sms_code')
|
|
|
|
// 发送短信验证码
|
|
const sendSmsCode = async () => {
|
|
captchaForm.mobile = props.mobile || ''
|
|
captchaForm.email = props.email || ''
|
|
|
|
if (!canSendCode.value) return
|
|
|
|
try {
|
|
isLoading.value = true
|
|
const response = await sendSmsVerificationCode(captchaForm) as any
|
|
|
|
if (response?.code !== -1) {
|
|
smsValue.value = response.data.key
|
|
startCountdown()
|
|
closeCaptchaDialog()
|
|
} else {
|
|
refreshCaptcha()
|
|
}
|
|
} catch (error) {
|
|
refreshCaptcha()
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// 开始倒计时
|
|
const startCountdown = () => {
|
|
canSendCode.value = false
|
|
countdownTimer = setInterval(() => {
|
|
if (seconds.value > 0) {
|
|
seconds.value -= 1
|
|
buttonText.value = `${seconds.value}${t('sms_code_change_text')}`
|
|
} else {
|
|
resetCountdown()
|
|
}
|
|
}, 1000) as any
|
|
}
|
|
|
|
// 重置倒计时
|
|
const resetCountdown = () => {
|
|
clearInterval(countdownTimer)
|
|
seconds.value = 90
|
|
canSendCode.value = true
|
|
buttonText.value = t('get_sms_code')
|
|
}
|
|
|
|
// 刷新验证码
|
|
const refreshCaptcha = async () => {
|
|
try {
|
|
// 这里需要调用验证码API,暂时使用空字符串
|
|
captchaImage.value = ''
|
|
captchaForm.captcha_code = ''
|
|
} catch (error) {
|
|
console.error('刷新验证码失败:', error)
|
|
}
|
|
}
|
|
|
|
// 处理发送验证码点击
|
|
const handleSendCode = () => {
|
|
emit('click')
|
|
showCaptchaDialog.value = true
|
|
refreshCaptcha()
|
|
}
|
|
|
|
// 确认发送验证码
|
|
const confirmSendCode = async () => {
|
|
await captchaFormRef.value?.validate(async (valid) => {
|
|
if (valid) {
|
|
await sendSmsCode()
|
|
}
|
|
})
|
|
}
|
|
|
|
// 关闭验证码对话框
|
|
const closeCaptchaDialog = () => {
|
|
showCaptchaDialog.value = false
|
|
captchaForm.captcha_code = ''
|
|
}
|
|
|
|
// 暴露方法给父组件
|
|
defineExpose({
|
|
send: sendSmsCode
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.sms-code-container {
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.sms-code-button {
|
|
font-size: 12px;
|
|
padding: 0;
|
|
height: auto;
|
|
}
|
|
|
|
.captcha-dialog {
|
|
:deep(.el-dialog__header) {
|
|
padding: 20px 20px 10px;
|
|
}
|
|
|
|
:deep(.el-dialog__body) {
|
|
padding: 10px 20px;
|
|
}
|
|
}
|
|
|
|
.captcha-form {
|
|
width: 100%;
|
|
}
|
|
|
|
.captcha-form-item {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.captcha-input {
|
|
:deep(.el-input__wrapper) {
|
|
padding-right: 0;
|
|
}
|
|
}
|
|
|
|
.captcha-image-wrapper {
|
|
padding: 5px 0;
|
|
line-height: 1;
|
|
}
|
|
|
|
.captcha-image {
|
|
height: 30px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
}
|
|
|
|
.cancel-button,
|
|
.confirm-button {
|
|
min-width: 80px;
|
|
}
|
|
</style> |