chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
<template>
|
||||
<view class="captcha-verify">
|
||||
<view class="captcha-verify-header">
|
||||
<!-- 返回按钮 -->
|
||||
<view class="back-box" @tap="handleBack">
|
||||
<text class="iconfont icon-a-Chevronleft"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="captcha-verify-content">
|
||||
<view class="title">
|
||||
{{
|
||||
phoneOrEmail?.includes("@")
|
||||
? t("Mobile.Auth.inputEmailCode")
|
||||
: t("Mobile.Auth.inputSmsCode")
|
||||
}}
|
||||
</view>
|
||||
<view class="phone-info">
|
||||
{{ t("Mobile.Auth.codeSentToPrefix") }}
|
||||
{{
|
||||
phoneOrEmail?.includes("@")
|
||||
? `${t("Mobile.Auth.codeSentToEmailTarget")} `
|
||||
: `${t("Mobile.Auth.codeSentToPhoneTarget")} `
|
||||
}}
|
||||
{{ `${!phoneOrEmail?.includes("@") ? "86" : ""} ${phoneOrEmail}` }}
|
||||
</view>
|
||||
<view class="captcha-input-container">
|
||||
<input
|
||||
ref="captchaInputRef"
|
||||
class="captcha-input"
|
||||
:class="{ active: isFocused }"
|
||||
type="number"
|
||||
pattern="[0-9]*"
|
||||
inputmode="numeric"
|
||||
maxlength="6"
|
||||
:value="captchaCode"
|
||||
:focus="isFocused"
|
||||
:adjust-position="false"
|
||||
:hold-keyboard="true"
|
||||
cursor-spacing="0"
|
||||
:placeholder="t('Mobile.Auth.inputVerifyCode')"
|
||||
cursor-color="#5147FF"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@paste="handlePaste"
|
||||
@longpress="handleLongPress"
|
||||
/>
|
||||
</view>
|
||||
<view class="resend-container">
|
||||
<text class="countdown" v-if="countdown > 0">{{
|
||||
t("Mobile.Auth.resendAfterSeconds", { seconds: countdown })
|
||||
}}</text>
|
||||
<text
|
||||
class="resend-btn"
|
||||
:class="{ disabled: countdown > 0 }"
|
||||
@tap="handleResend"
|
||||
>{{ t("Mobile.Auth.resend") }}</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts" setup>
|
||||
import { apiSendCode } from "@/servers/account";
|
||||
import { SUCCESS_CODE, AGENT_NOT_EXIST } from "@/constants/codes.constants";
|
||||
import { useI18n } from "@/utils/i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义组件props
|
||||
interface Props {
|
||||
phoneOrEmail?: string;
|
||||
tenantConfigInfo?: TenantConfigInfo;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
phoneOrEmail: "",
|
||||
tenantConfigInfo: null,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["verify-login", "back"]);
|
||||
|
||||
const captchaCode = ref<string>("");
|
||||
const countdown = ref<number>(59);
|
||||
let countdownTimer: number | null = null;
|
||||
const captchaInputRef = ref<any>(null);
|
||||
const isFocused = ref<boolean>(false);
|
||||
|
||||
// 判断配置信息是否邮箱登录
|
||||
const isEmailAuth = computed(() => {
|
||||
if (!props.tenantConfigInfo) {
|
||||
return false;
|
||||
}
|
||||
return props.tenantConfigInfo?.authType === 3;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
open();
|
||||
});
|
||||
|
||||
const open = () => {
|
||||
captchaCode.value = "";
|
||||
startCountdown();
|
||||
nextTick(() => {
|
||||
focusInput();
|
||||
});
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
clearCountdown();
|
||||
emit("back");
|
||||
};
|
||||
|
||||
const handleInput = async (event: any) => {
|
||||
let value = event.detail.value || "";
|
||||
|
||||
// 强制过滤非数字字符
|
||||
value = value.replace(/[^0-9]/g, "");
|
||||
|
||||
// 限制最大长度为6位
|
||||
if (value.length > 6) {
|
||||
value = value.slice(0, 6);
|
||||
}
|
||||
|
||||
captchaCode.value = value;
|
||||
|
||||
// 强制更新输入框显示值
|
||||
// #ifdef MP-WEIXIN
|
||||
if (captchaInputRef.value) {
|
||||
captchaInputRef.value.value = value;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 检查是否输入完成(6位数字)
|
||||
if (value.length === 6 && /^\d{6}$/.test(value)) {
|
||||
emit("verify-login", value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
isFocused.value = true;
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
isFocused.value = false;
|
||||
};
|
||||
|
||||
const handlePaste = async (event: any) => {
|
||||
// #ifdef H5
|
||||
// H5平台使用 clipboardData
|
||||
event.preventDefault();
|
||||
let pasteData = "";
|
||||
if (event.clipboardData && event.clipboardData.getData) {
|
||||
pasteData = event.clipboardData.getData("text");
|
||||
await processPasteData(pasteData);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
// 微信小程序和App平台在用户点击粘贴后触发此事件
|
||||
// 延迟一下确保粘贴操作完成后再读取
|
||||
setTimeout(async () => {
|
||||
await getClipboardAndPaste();
|
||||
}, 100);
|
||||
// #endif
|
||||
};
|
||||
|
||||
const getClipboardAndPaste = async () => {
|
||||
try {
|
||||
const res = await uni.getClipboardData();
|
||||
await processPasteData(res.data);
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.clipboardGetFailed"),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const processPasteData = async (pasteData: string) => {
|
||||
// 验证粘贴内容是否为6位数字
|
||||
if (pasteData && /^\d{6}$/.test(pasteData)) {
|
||||
// 直接设置验证码
|
||||
captchaCode.value = pasteData;
|
||||
|
||||
// 强制更新输入框显示值
|
||||
// #ifdef MP-WEIXIN
|
||||
if (captchaInputRef.value) {
|
||||
captchaInputRef.value.value = pasteData;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 触发验证
|
||||
emit("verify-login", pasteData);
|
||||
|
||||
// 提示用户
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.codeAutoFilled"),
|
||||
icon: "success",
|
||||
duration: 1500,
|
||||
});
|
||||
} else {
|
||||
// 粘贴内容不是6位数字,提示用户
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.pasteSixDigits"),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleLongPress = async (event: any) => {
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
// 长按时直接读取剪贴板并询问是否填充
|
||||
try {
|
||||
const res = await uni.getClipboardData();
|
||||
const clipData = res.data;
|
||||
// 检查剪贴板是否包含6位数字验证码
|
||||
if (clipData && /^\d{6}$/.test(clipData)) {
|
||||
// 直接填充,不询问
|
||||
await processPasteData(clipData);
|
||||
} else if (clipData) {
|
||||
// 剪贴板有内容但不是6位数字
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.clipboardNotSixDigits"),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.clipboardEmpty"),
|
||||
icon: "none",
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.clipboardReadFailed"),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
};
|
||||
|
||||
const startCountdown = () => {
|
||||
countdown.value = 59;
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearCountdown();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const clearCountdown = () => {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 兼容微信小程序和H5的聚焦方法
|
||||
const focusInput = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序使用 isFocused 响应式变量控制聚焦
|
||||
isFocused.value = true;
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5平台使用 ref 引用
|
||||
if (captchaInputRef.value && captchaInputRef.value.focus) {
|
||||
captchaInputRef.value.focus();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// App平台使用 ref 引用
|
||||
if (captchaInputRef.value && captchaInputRef.value.focus) {
|
||||
captchaInputRef.value.focus();
|
||||
}
|
||||
// #endif
|
||||
};
|
||||
|
||||
const handleResend = async () => {
|
||||
if (countdown.value > 0) return;
|
||||
const params = {
|
||||
type: "LOGIN_OR_REGISTER",
|
||||
// "phone": props.phoneOrEmail
|
||||
};
|
||||
|
||||
if (isEmailAuth.value) {
|
||||
params.email = props.phoneOrEmail;
|
||||
} else {
|
||||
params.phone = props.phoneOrEmail;
|
||||
}
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: t("Mobile.Auth.sending"),
|
||||
mask: false,
|
||||
});
|
||||
// 发送验证码
|
||||
const { code, data, message } = await apiSendCode(params);
|
||||
if (isEmailAuth.value) {
|
||||
// 邮箱验证码
|
||||
if (code === SUCCESS_CODE) {
|
||||
startCountdown();
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.resendSuccess"),
|
||||
icon: "success",
|
||||
});
|
||||
} else if (code === AGENT_NOT_EXIST) {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: "none",
|
||||
duration: 5000,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 手机验证码
|
||||
if (code === SUCCESS_CODE) {
|
||||
startCountdown();
|
||||
uni.showToast({
|
||||
title: t("Mobile.Auth.resendSuccess"),
|
||||
icon: "success",
|
||||
});
|
||||
} else {
|
||||
// 手机验证失败提示消息
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
clearCountdown();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.captcha-verify {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.captcha-verify-header {
|
||||
.back-box {
|
||||
display: inline-block;
|
||||
width: 58rpx;
|
||||
height: 58rpx;
|
||||
line-height: 58rpx;
|
||||
text-align: center;
|
||||
background-color: rgba(12, 20, 102, 0.04);
|
||||
border-radius: 50%;
|
||||
|
||||
.iconfont {
|
||||
font-size: 40rpx;
|
||||
line-height: 58rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.captcha-verify-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
line-height: 56rpx;
|
||||
font-weight: 600;
|
||||
color: rgba(21, 23, 31, 1);
|
||||
margin-bottom: 24rpx;
|
||||
line-height: 64rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.phone-info {
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: rgba(21, 23, 31, 0.7);
|
||||
margin-bottom: 80rpx;
|
||||
line-height: 44rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.captcha-input-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 80rpx;
|
||||
|
||||
.captcha-input {
|
||||
width: 100%;
|
||||
// max-width: 600rpx;
|
||||
height: 88rpx;
|
||||
border: 2rpx solid transparent;
|
||||
border-radius: 16rpx;
|
||||
text-align: left;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
color: rgba(21, 23, 31, 1);
|
||||
background-color: rgba(12, 20, 102, 0.04);
|
||||
padding: 0 24rpx;
|
||||
// letter-spacing: 8rpx;
|
||||
|
||||
&.active {
|
||||
border-color: rgba(81, 71, 255, 1);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: rgba(81, 71, 255, 1);
|
||||
background-color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resend-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
line-height: 44rpx;
|
||||
color: rgba(21, 23, 31, 0.7);
|
||||
|
||||
.countdown {
|
||||
}
|
||||
|
||||
.resend-btn {
|
||||
cursor: pointer;
|
||||
color: #5147ff;
|
||||
&.disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user