整合前端仓库并保留提交历史

This commit is contained in:
2026-08-31 11:52:41 +08:00
172 changed files with 120216 additions and 1 deletions
@@ -0,0 +1,280 @@
<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>