Merge branch 'main' of http://1.117.67.95:3000/team/tyc-webview-v2
This commit is contained in:
76
index.html
76
index.html
@@ -1,21 +1,33 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
<link
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
rel="apple-touch-icon"
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
sizes="180x180"
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
href="/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=3, user-scalable=no"
|
content="width=device-width, initial-scale=1, maximum-scale=3, user-scalable=no"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 基础SEO信息 -->
|
<!-- 基础SEO信息 -->
|
||||||
<title>
|
<title>天远查官网_企业与婚姻关联风险核验_综合履约背景核验</title>
|
||||||
天远查官网_企业与婚姻关联风险核验_综合履约背景核验
|
|
||||||
</title>
|
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。"
|
content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。"
|
||||||
@@ -31,17 +43,29 @@
|
|||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://www.tianyuancha.cn/" />
|
<meta property="og:url" content="https://www.zhinengcha.cn/" />
|
||||||
<meta property="og:title" content="天远查官网_企业与婚姻关联风险核验_综合履约背景核验" />
|
<meta
|
||||||
<meta property="og:description" content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。" />
|
property="og:title"
|
||||||
|
content="天远查官网_企业与婚姻关联风险核验_综合履约背景核验"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。"
|
||||||
|
/>
|
||||||
<meta property="og:site_name" content="天远查" />
|
<meta property="og:site_name" content="天远查" />
|
||||||
<meta property="og:locale" content="zh_CN" />
|
<meta property="og:locale" content="zh_CN" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta property="twitter:card" content="summary" />
|
<meta property="twitter:card" content="summary" />
|
||||||
<meta property="twitter:url" content="https://www.tianyuancha.cn/" />
|
<meta property="twitter:url" content="https://www.zhinengcha.cn/" />
|
||||||
<meta property="twitter:title" content="天远查官网_企业与婚姻关联风险核验_综合履约背景核验" />
|
<meta
|
||||||
<meta property="twitter:description" content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。" />
|
property="twitter:title"
|
||||||
|
content="天远查官网_企业与婚姻关联风险核验_综合履约背景核验"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="twitter:description"
|
||||||
|
content="天远查官网(TianYuanCha)聚合官方公示数据,专注于商业安全与资产背调。提供企业工商画像、婚姻状态关联风险、司法涉诉筛查及配偶债务核验。数据实时同步,助您精准规避投资、交易及家庭结合中的经济与法律风险。"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 其他重要meta标签 -->
|
<!-- 其他重要meta标签 -->
|
||||||
<meta name="theme-color" content="#3498db" />
|
<meta name="theme-color" content="#3498db" />
|
||||||
@@ -57,11 +81,11 @@
|
|||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "WebSite",
|
"@type": "WebSite",
|
||||||
"name": "天远查",
|
"name": "天远查",
|
||||||
"url": "https://www.tianyuancha.cn/",
|
"url": "https://www.zhinengcha.cn/",
|
||||||
"description": "专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用",
|
"description": "专业大数据风险报告查询与代理平台,支持个人信用查询、小微企业风控、贷前风险背调等多场景报告应用",
|
||||||
"potentialAction": {
|
"potentialAction": {
|
||||||
"@type": "SearchAction",
|
"@type": "SearchAction",
|
||||||
"target": "https://www.tianyuancha.cn/search?q={search_term_string}",
|
"target": "https://www.zhinengcha.cn/search?q={search_term_string}",
|
||||||
"query-input": "required name=search_term_string"
|
"query-input": "required name=search_term_string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +96,7 @@
|
|||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
"name": "天远查",
|
"name": "天远查",
|
||||||
"url": "https://www.tianyuancha.cn/",
|
"url": "https://www.zhinengcha.cn/",
|
||||||
"description": "专业大数据风险报告查询与代理平台,支持个人和企业多场景风控应用"
|
"description": "专业大数据风险报告查询与代理平台,支持个人和企业多场景风控应用"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -82,12 +106,18 @@
|
|||||||
window.jWeixin = window.wx;
|
window.jWeixin = window.wx;
|
||||||
delete window.wx;
|
delete window.wx;
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
window.AliyunCaptchaConfig = { region: "cn", prefix: "12zxnj" };
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js"
|
||||||
|
></script>
|
||||||
<!-- 预加载关键资源 -->
|
<!-- 预加载关键资源 -->
|
||||||
<link rel="preconnect" href="https://www.tianyuancha.cn">
|
<link rel="preconnect" href="https://www.zhinengcha.cn" />
|
||||||
<link rel="preconnect" href="https://res.wx.qq.com">
|
<link rel="preconnect" href="https://res.wx.qq.com" />
|
||||||
<link rel="dns-prefetch" href="https://www.tianyuancha.cn">
|
<link rel="dns-prefetch" href="https://www.zhinengcha.cn" />
|
||||||
<link rel="dns-prefetch" href="https://res.wx.qq.com">
|
<link rel="dns-prefetch" href="https://res.wx.qq.com" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 基础样式 */
|
/* 基础样式 */
|
||||||
@@ -181,6 +211,8 @@
|
|||||||
<div class="loading-text">加载中</div>
|
<div class="loading-text">加载中</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<!-- 阿里云验证码渲染容器(弹出式可隐藏) -->
|
||||||
|
<div id="captcha-element" style="display: none;"></div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
1
src/auto-imports.d.ts
vendored
1
src/auto-imports.d.ts
vendored
@@ -117,6 +117,7 @@ declare global {
|
|||||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||||
const useAgent: typeof import('./composables/useAgent.js')['useAgent']
|
const useAgent: typeof import('./composables/useAgent.js')['useAgent']
|
||||||
const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore']
|
const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore']
|
||||||
|
const useAliyunCaptcha: typeof import('./composables/useAliyunCaptcha.js')['useAliyunCaptcha']
|
||||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||||
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
|
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
|
||||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||||
|
|||||||
@@ -461,6 +461,7 @@ import { useDialogStore } from "@/stores/dialogStore";
|
|||||||
import { useEnv } from "@/composables/useEnv";
|
import { useEnv } from "@/composables/useEnv";
|
||||||
import { showConfirmDialog, showToast, DatePicker } from "vant";
|
import { showConfirmDialog, showToast, DatePicker } from "vant";
|
||||||
import { useInquireForm } from "@/composables/useInquireForm";
|
import { useInquireForm } from "@/composables/useInquireForm";
|
||||||
|
import { useAliyunCaptcha } from "@/composables/useAliyunCaptcha";
|
||||||
|
|
||||||
import Payment from "@/components/Payment.vue";
|
import Payment from "@/components/Payment.vue";
|
||||||
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
|
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
|
||||||
@@ -557,6 +558,7 @@ const featureData = computed(() => props.featureData || {});
|
|||||||
|
|
||||||
// 使用通用查询表单 composable(根据 feature 自动决定字段)
|
// 使用通用查询表单 composable(根据 feature 自动决定字段)
|
||||||
const { formData, isPhoneNumberValid, isIdCardValid, isHasInput, buildRequestPayload } = useInquireForm(feature);
|
const { formData, isPhoneNumberValid, isIdCardValid, isHasInput, buildRequestPayload } = useInquireForm(feature);
|
||||||
|
const { runWithCaptcha } = useAliyunCaptcha();
|
||||||
|
|
||||||
const userTypeOptions = [
|
const userTypeOptions = [
|
||||||
{ text: "ETC开户人", value: "1" },
|
{ text: "ETC开户人", value: "1" },
|
||||||
@@ -998,69 +1000,77 @@ function handleSubmit() {
|
|||||||
if (!userStore.mobile) {
|
if (!userStore.mobile) {
|
||||||
pendingPayment.value = true;
|
pendingPayment.value = true;
|
||||||
dialogStore.openBindPhone();
|
dialogStore.openBindPhone();
|
||||||
} else {
|
} else if (isHasInput("verificationCode")) {
|
||||||
submitRequest();
|
submitRequest();
|
||||||
|
} else {
|
||||||
|
// 无短信验证码时,查询前先过滑块
|
||||||
|
runWithCaptcha(
|
||||||
|
(captchaVerifyParam) => doQueryPost(captchaVerifyParam),
|
||||||
|
(res) => {
|
||||||
|
if (res?.code === 200 && res?.data) handleQueryResult(res);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitRequest() {
|
function handleQueryResult(res) {
|
||||||
// 根据当前 feature 与字段配置组装请求体
|
queryId.value = res.data.id;
|
||||||
|
if (props.type === "promotion") {
|
||||||
|
localStorage.setItem("token", res.data.accessToken);
|
||||||
|
localStorage.setItem("refreshAfter", res.data.refreshAfter);
|
||||||
|
localStorage.setItem("accessExpire", res.data.accessExpire);
|
||||||
|
}
|
||||||
|
showPayment.value = true;
|
||||||
|
emit("submit-success", res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doQueryPost(captchaVerifyParam) {
|
||||||
const req = buildRequestPayload();
|
const req = buildRequestPayload();
|
||||||
const reqStr = JSON.stringify(req);
|
const reqStr = JSON.stringify(req);
|
||||||
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
|
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
|
||||||
|
let apiUrl = "";
|
||||||
let apiUrl = '';
|
const requestData = { data: encodeData };
|
||||||
let requestData = { data: encodeData };
|
if (captchaVerifyParam) requestData.captchaVerifyParam = captchaVerifyParam;
|
||||||
|
if (props.type === "promotion") {
|
||||||
if (props.type === 'promotion') {
|
|
||||||
apiUrl = `/query/service_agent/${props.feature}`;
|
apiUrl = `/query/service_agent/${props.feature}`;
|
||||||
requestData.agent_identifier = props.linkIdentifier;
|
requestData.agent_identifier = props.linkIdentifier;
|
||||||
} else {
|
} else {
|
||||||
apiUrl = `/query/service/${props.feature}`;
|
apiUrl = `/query/service/${props.feature}`;
|
||||||
}
|
}
|
||||||
|
return useApiFetch(apiUrl).post(requestData).json();
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = await useApiFetch(apiUrl)
|
async function submitRequest() {
|
||||||
.post(requestData)
|
const { data, error } = await doQueryPost();
|
||||||
.json();
|
if (data.value?.code === 200 && data.value?.data) {
|
||||||
|
handleQueryResult(data.value);
|
||||||
if (data.value.code === 200) {
|
|
||||||
queryId.value = data.value.data.id;
|
|
||||||
|
|
||||||
// 推广查询需要保存token
|
|
||||||
if (props.type === 'promotion') {
|
|
||||||
localStorage.setItem("token", data.value.data.accessToken);
|
|
||||||
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
|
|
||||||
localStorage.setItem("accessExpire", data.value.data.accessExpire);
|
|
||||||
}
|
|
||||||
|
|
||||||
showPayment.value = true;
|
|
||||||
emit('submit-success', data.value.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendVerificationCode() {
|
function sendVerificationCode() {
|
||||||
if (isCountingDown.value || !isPhoneNumberValid.value) return;
|
if (isCountingDown.value || !isPhoneNumberValid.value) return;
|
||||||
if (!isPhoneNumberValid.value) {
|
if (!isPhoneNumberValid.value) {
|
||||||
showToast({ message: "请输入有效的手机号" });
|
showToast({ message: "请输入有效的手机号" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
runWithCaptcha(
|
||||||
const { data, error } = await useApiFetch("/auth/sendSms")
|
(captchaVerifyParam) =>
|
||||||
.post({ mobile: formData.mobile, actionType: "query" })
|
useApiFetch("/auth/sendSms")
|
||||||
.json();
|
.post({ mobile: formData.mobile, actionType: "query", captchaVerifyParam })
|
||||||
|
.json(),
|
||||||
if (!error.value && data.value.code === 200) {
|
(res) => {
|
||||||
|
if (res?.code === 200) {
|
||||||
showToast({ message: "验证码发送成功", type: "success" });
|
showToast({ message: "验证码发送成功", type: "success" });
|
||||||
startCountdown();
|
startCountdown();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const verificationCodeInput = document.getElementById('verificationCode');
|
const el = document.getElementById("verificationCode");
|
||||||
if (verificationCodeInput) {
|
if (el) el.focus();
|
||||||
verificationCodeInput.focus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast({ message: "验证码发送失败,请重试" });
|
showToast({ message: res?.msg || "验证码发送失败,请重试" });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { ref, computed, nextTick } from 'vue'
|
|||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
import { useDialogStore } from '@/stores/dialogStore'
|
import { useDialogStore } from '@/stores/dialogStore'
|
||||||
import { useUserStore } from '@/stores/userStore'
|
import { useUserStore } from '@/stores/userStore'
|
||||||
|
import { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
|
||||||
|
|
||||||
const emit = defineEmits(['login-success'])
|
const emit = defineEmits(['login-success'])
|
||||||
const dialogStore = useDialogStore()
|
const dialogStore = useDialogStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const { runWithCaptcha } = useAliyunCaptcha()
|
||||||
|
|
||||||
const phoneNumber = ref('')
|
const phoneNumber = ref('')
|
||||||
const verificationCode = ref('')
|
const verificationCode = ref('')
|
||||||
@@ -35,31 +37,30 @@ const canLogin = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sendVerificationCode() {
|
function sendVerificationCode() {
|
||||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||||
if (!isPhoneNumberValid.value) {
|
if (!isPhoneNumberValid.value) {
|
||||||
showToast({ message: "请输入有效的手机号" });
|
showToast({ message: "请输入有效的手机号" });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { data, error } = await useApiFetch('auth/sendSms')
|
runWithCaptcha(
|
||||||
.post({ mobile: phoneNumber.value, actionType: 'login' })
|
(captchaVerifyParam) =>
|
||||||
.json()
|
useApiFetch('auth/sendSms')
|
||||||
|
.post({ mobile: phoneNumber.value, actionType: 'login', captchaVerifyParam })
|
||||||
if (data.value && !error.value) {
|
.json(),
|
||||||
if (data.value.code === 200) {
|
(res) => {
|
||||||
|
if (res?.code === 200) {
|
||||||
showToast({ message: "获取成功" });
|
showToast({ message: "获取成功" });
|
||||||
startCountdown()
|
startCountdown();
|
||||||
// 聚焦到验证码输入框
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const verificationCodeInput = document.getElementById('verificationCode');
|
const el = document.getElementById('verificationCode');
|
||||||
if (verificationCodeInput) {
|
if (el) el.focus();
|
||||||
verificationCodeInput.focus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast(data.value.msg)
|
showToast(res?.msg || "获取失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCountdown() {
|
function startCountdown() {
|
||||||
@@ -95,31 +96,17 @@ async function handleLogin() {
|
|||||||
showToast({ message: "请先同意用户协议" });
|
showToast({ message: "请先同意用户协议" });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 直接执行登录逻辑
|
|
||||||
await performLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行实际的登录逻辑
|
|
||||||
async function performLogin() {
|
|
||||||
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
|
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
|
||||||
.post({ mobile: phoneNumber.value, code: verificationCode.value })
|
.post({ mobile: phoneNumber.value, code: verificationCode.value })
|
||||||
.json()
|
.json();
|
||||||
|
if (data.value && !error.value && data.value.code === 200 && data.value.data) {
|
||||||
if (data.value && !error.value) {
|
localStorage.setItem('token', data.value.data.accessToken);
|
||||||
if (data.value.code === 200) {
|
localStorage.setItem('refreshAfter', data.value.data.refreshAfter);
|
||||||
localStorage.setItem('token', data.value.data.accessToken)
|
localStorage.setItem('accessExpire', data.value.data.accessExpire);
|
||||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
await userStore.fetchUserInfo();
|
||||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
|
||||||
|
|
||||||
// 更新用户信息
|
|
||||||
await userStore.fetchUserInfo()
|
|
||||||
|
|
||||||
showToast({ message: "登录成功" });
|
showToast({ message: "登录成功" });
|
||||||
closeDialog();
|
closeDialog();
|
||||||
emit('login-success');
|
emit('login-success');
|
||||||
} else {
|
|
||||||
showToast(data.value.msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
170
src/composables/useAliyunCaptcha.js
Normal file
170
src/composables/useAliyunCaptcha.js
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { showToast, showLoadingToast, closeToast } from "vant";
|
||||||
|
import useApiFetch from "@/composables/useApiFetch";
|
||||||
|
|
||||||
|
// 阿里云验证码场景 ID
|
||||||
|
const ALIYUN_CAPTCHA_SCENE_ID = "wynt39to";
|
||||||
|
// 是否启用加密模式(通过环境变量控制,非加密模式时前端不调用后端获取 EncryptedSceneId)
|
||||||
|
const ENABLE_ENCRYPTED =
|
||||||
|
import.meta.env.VITE_ALIYUN_CAPTCHA_ENCRYPTED === "false";
|
||||||
|
|
||||||
|
let captchaInitialised = false;
|
||||||
|
/** 首次初始化后,SDK 会异步调用 getInstance,用此 Promise 在实例就绪后再 show */
|
||||||
|
let captchaReadyPromise = null;
|
||||||
|
let captchaReadyResolve = null;
|
||||||
|
|
||||||
|
async function ensureCaptchaInit() {
|
||||||
|
if (captchaInitialised || typeof window === "undefined") return;
|
||||||
|
if (typeof window.initAliyunCaptcha !== "function") return;
|
||||||
|
|
||||||
|
captchaInitialised = true;
|
||||||
|
window.captcha = null;
|
||||||
|
window.__lastBizResponse = null;
|
||||||
|
window.__onCaptchaBizSuccess = null;
|
||||||
|
captchaReadyPromise = new Promise((resolve) => {
|
||||||
|
captchaReadyResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 非加密模式:仅传 SceneId,不调用后端接口
|
||||||
|
if (!ENABLE_ENCRYPTED) {
|
||||||
|
window.initAliyunCaptcha({
|
||||||
|
SceneId: ALIYUN_CAPTCHA_SCENE_ID,
|
||||||
|
mode: "popup",
|
||||||
|
element: "#captcha-element",
|
||||||
|
getInstance(instance) {
|
||||||
|
window.captcha = instance;
|
||||||
|
if (typeof captchaReadyResolve === "function") {
|
||||||
|
captchaReadyResolve();
|
||||||
|
captchaReadyResolve = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
captchaVerifyCallback(param) {
|
||||||
|
return typeof window.__captchaVerifyCallback === "function"
|
||||||
|
? window.__captchaVerifyCallback(param)
|
||||||
|
: Promise.resolve({
|
||||||
|
captchaResult: false,
|
||||||
|
bizResult: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onBizResultCallback(bizResult) {
|
||||||
|
if (typeof window.__onBizResultCallback === "function") {
|
||||||
|
window.__onBizResultCallback(bizResult);
|
||||||
|
}
|
||||||
|
window.__lastBizResponse = null;
|
||||||
|
window.__onCaptchaBizSuccess = null;
|
||||||
|
},
|
||||||
|
slideStyle: { width: 360, height: 40 },
|
||||||
|
language: "cn",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密模式:先从后端获取 EncryptedSceneId,再初始化
|
||||||
|
const { data, error } = await useApiFetch("/captcha/encryptedSceneId")
|
||||||
|
.post()
|
||||||
|
.json();
|
||||||
|
const resp = data?.value;
|
||||||
|
const encryptedSceneId = resp?.data?.encryptedSceneId;
|
||||||
|
if (error?.value || !encryptedSceneId) {
|
||||||
|
showToast({ message: "获取验证码参数失败,请稍后重试" });
|
||||||
|
captchaInitialised = false;
|
||||||
|
captchaReadyPromise = null;
|
||||||
|
captchaReadyResolve = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.initAliyunCaptcha({
|
||||||
|
SceneId: ALIYUN_CAPTCHA_SCENE_ID,
|
||||||
|
EncryptedSceneId: encryptedSceneId,
|
||||||
|
mode: "popup",
|
||||||
|
element: "#captcha-element",
|
||||||
|
getInstance(instance) {
|
||||||
|
window.captcha = instance;
|
||||||
|
if (typeof captchaReadyResolve === "function") {
|
||||||
|
captchaReadyResolve();
|
||||||
|
captchaReadyResolve = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
captchaVerifyCallback(param) {
|
||||||
|
return typeof window.__captchaVerifyCallback === "function"
|
||||||
|
? window.__captchaVerifyCallback(param)
|
||||||
|
: Promise.resolve({ captchaResult: false, bizResult: false });
|
||||||
|
},
|
||||||
|
onBizResultCallback(bizResult) {
|
||||||
|
if (typeof window.__onBizResultCallback === "function") {
|
||||||
|
window.__onBizResultCallback(bizResult);
|
||||||
|
}
|
||||||
|
window.__lastBizResponse = null;
|
||||||
|
window.__onCaptchaBizSuccess = null;
|
||||||
|
},
|
||||||
|
slideStyle: { width: 360, height: 40 },
|
||||||
|
language: "cn",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云滑块验证码通用封装。
|
||||||
|
* 依赖 index.html 中已加载的 AliyunCaptcha.js;初始化在首次调起时执行。
|
||||||
|
*
|
||||||
|
* @param { (captchaVerifyParam: string) => Promise<{ data: Ref, error: Ref }> } bizVerify - 业务请求函数,接收滑块参数,返回 useApiFetch 的 { data, error }
|
||||||
|
* @param { (res: any) => void } [onSuccess] - 业务成功回调(code===200 时调用,传入接口返回的 data.value)
|
||||||
|
*/
|
||||||
|
export function useAliyunCaptcha() {
|
||||||
|
/**
|
||||||
|
* 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。
|
||||||
|
*/
|
||||||
|
async function runWithCaptcha(bizVerify, onSuccess) {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
showToast({ message: "验证码仅支持浏览器环境" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = showLoadingToast({
|
||||||
|
message: "安全验证加载中...",
|
||||||
|
forbidClick: true,
|
||||||
|
duration: 0,
|
||||||
|
loadingType: "spinner",
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.__captchaVerifyCallback = async (captchaVerifyParam) => {
|
||||||
|
window.__lastBizResponse = null;
|
||||||
|
const { data, error } = await bizVerify(captchaVerifyParam);
|
||||||
|
const result = data?.value ?? data;
|
||||||
|
if (error?.value || !result) {
|
||||||
|
return { captchaResult: false, bizResult: false };
|
||||||
|
}
|
||||||
|
window.__lastBizResponse = result;
|
||||||
|
const captchaOk = result.captchaVerifyResult !== false;
|
||||||
|
const bizOk = result.code === 200;
|
||||||
|
return { captchaResult: captchaOk, bizResult: bizOk };
|
||||||
|
};
|
||||||
|
|
||||||
|
window.__onBizResultCallback = (bizResult) => {
|
||||||
|
if (
|
||||||
|
bizResult === true &&
|
||||||
|
window.__lastBizResponse &&
|
||||||
|
typeof window.__onCaptchaBizSuccess === "function"
|
||||||
|
) {
|
||||||
|
window.__onCaptchaBizSuccess(window.__lastBizResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await ensureCaptchaInit();
|
||||||
|
|
||||||
|
// 首次初始化时 SDK 会异步调用 getInstance,需等待实例就绪后再 show
|
||||||
|
if (captchaReadyPromise) {
|
||||||
|
await captchaReadyPromise;
|
||||||
|
captchaReadyPromise = null;
|
||||||
|
}
|
||||||
|
if (!window.captcha) {
|
||||||
|
showToast({ message: "验证码未加载,请刷新页面重试" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.__onCaptchaBizSuccess = onSuccess;
|
||||||
|
window.captcha.show();
|
||||||
|
} finally {
|
||||||
|
closeToast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { runWithCaptcha };
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onUnmounted, nextTick } from 'vue'
|
import { ref, computed, onUnmounted, nextTick } from 'vue'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
|
import { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { runWithCaptcha } = useAliyunCaptcha()
|
||||||
const phoneNumber = ref('')
|
const phoneNumber = ref('')
|
||||||
const verificationCode = ref('')
|
const verificationCode = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
@@ -30,31 +32,30 @@ const canLogin = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sendVerificationCode() {
|
function sendVerificationCode() {
|
||||||
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
if (isCountingDown.value || !isPhoneNumberValid.value) return
|
||||||
if (!isPhoneNumberValid.value) {
|
if (!isPhoneNumberValid.value) {
|
||||||
showToast({ message: "请输入有效的手机号" });
|
showToast({ message: "请输入有效的手机号" });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { data, error } = await useApiFetch('auth/sendSms')
|
runWithCaptcha(
|
||||||
.post({ mobile: phoneNumber.value, actionType: 'login' })
|
(captchaVerifyParam) =>
|
||||||
.json()
|
useApiFetch('auth/sendSms')
|
||||||
|
.post({ mobile: phoneNumber.value, actionType: 'login', captchaVerifyParam })
|
||||||
if (data.value && !error.value) {
|
.json(),
|
||||||
if (data.value.code === 200) {
|
(res) => {
|
||||||
|
if (res?.code === 200) {
|
||||||
showToast({ message: "获取成功" });
|
showToast({ message: "获取成功" });
|
||||||
startCountdown()
|
startCountdown();
|
||||||
// 聚焦到验证码输入框
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const verificationCodeInput = document.getElementById('verificationCode');
|
const el = document.getElementById('verificationCode');
|
||||||
if (verificationCodeInput) {
|
if (el) el.focus();
|
||||||
verificationCodeInput.focus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast(data.value.msg)
|
showToast(res?.msg || "获取失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCountdown() {
|
function startCountdown() {
|
||||||
@@ -90,25 +91,17 @@ async function handleLogin() {
|
|||||||
showToast({ message: "请先同意用户协议" });
|
showToast({ message: "请先同意用户协议" });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 直接执行登录逻辑
|
|
||||||
await performLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行实际的登录逻辑
|
|
||||||
async function performLogin() {
|
|
||||||
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
|
const { data, error } = await useApiFetch('/user/mobileCodeLogin')
|
||||||
.post({ mobile: phoneNumber.value, code: verificationCode.value })
|
.post({ mobile: phoneNumber.value, code: verificationCode.value })
|
||||||
.json()
|
.json();
|
||||||
|
if (data.value && !error.value && data.value.code === 200 && data.value.data) {
|
||||||
if (data.value && !error.value) {
|
localStorage.setItem('token', data.value.data.accessToken);
|
||||||
if (data.value.code === 200) {
|
localStorage.setItem('refreshAfter', data.value.data.refreshAfter);
|
||||||
localStorage.setItem('token', data.value.data.accessToken)
|
localStorage.setItem('accessExpire', data.value.data.accessExpire);
|
||||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
window.location.href = '/';
|
||||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
|
||||||
window.location.href = '/'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toUserAgreement() {
|
function toUserAgreement() {
|
||||||
router.push(`/userAgreement`)
|
router.push(`/userAgreement`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user