|
@@ -0,0 +1,334 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <NConfigProvider :theme-overrides="themeOverrides">
|
|
|
|
|
+ <n-form ref="loginForm" :model="form" :rules="rules" :show-label="false">
|
|
|
|
|
+ <n-form-item ref="phoneRef" path="phone" label="">
|
|
|
|
|
+ <n-input v-model:value="form.phone" placeholder="请输入手机号" :allow-input="onlyAllowNumber">
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <n-icon size="24" class="input-icon mr-3">
|
|
|
|
|
+ <user />
|
|
|
|
|
+ </n-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </n-input>
|
|
|
|
|
+ </n-form-item>
|
|
|
|
|
+ <n-form-item path="code" label="" class="w-100">
|
|
|
|
|
+ <div class="flex flex-1">
|
|
|
|
|
+ <n-input
|
|
|
|
|
+ class="flex-1"
|
|
|
|
|
+ v-model:value="form.code"
|
|
|
|
|
+ placeholder="请输入验证码"
|
|
|
|
|
+ :allow-input="onlyAllowNumber"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <n-icon size="24" class="input-icon mr-3">
|
|
|
|
|
+ <Lock />
|
|
|
|
|
+ </n-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </n-input>
|
|
|
|
|
+ <div class="ml-3">
|
|
|
|
|
+ <n-button
|
|
|
|
|
+ style="width: 100px"
|
|
|
|
|
+ :disabled="countDown > 0"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ secondary
|
|
|
|
|
+ @click="verifyCaptcha"
|
|
|
|
|
+ :loading="sending"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ sending ? '' : countDown > 0 ? `${countDown}秒` : '发送验证码' }}
|
|
|
|
|
+ </n-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </n-form-item>
|
|
|
|
|
+ <div class="mt-3">
|
|
|
|
|
+ <n-button class="h-10" @click="submit" block type="primary" size="large" :loading="loading" circle>
|
|
|
|
|
+ 登录
|
|
|
|
|
+ </n-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <n-el class="agree mt-5 text-center text-xs">
|
|
|
|
|
+ <n-checkbox v-model:checked="agree">
|
|
|
|
|
+ 已阅读并同意
|
|
|
|
|
+ <span class="prim" @click.stop="">《用户服务协议》</span>和
|
|
|
|
|
+ <span class="prim" @click.stop="">《平台隐私协议》</span>
|
|
|
|
|
+ </n-checkbox>
|
|
|
|
|
+ </n-el>
|
|
|
|
|
+ </n-form>
|
|
|
|
|
+ <NModal v-model:show="showCpatchaModal">
|
|
|
|
|
+ <div class="w-11/12 md:w-4/5 max-w-sm">
|
|
|
|
|
+ <n-card title="请先完成滑动验证" :bordered="false" size="medium" role="dialog" aria-modal="true">
|
|
|
|
|
+ <NEl style="min-height: 55px">
|
|
|
|
|
+ <div id="nc"></div>
|
|
|
|
|
+ </NEl>
|
|
|
|
|
+ </n-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </NModal>
|
|
|
|
|
+ </NConfigProvider>
|
|
|
|
|
+</template>
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, Ref } from 'vue'
|
|
|
|
|
+import {
|
|
|
|
|
+ NForm,
|
|
|
|
|
+ NFormItem,
|
|
|
|
|
+ NInput,
|
|
|
|
|
+ NCheckbox,
|
|
|
|
|
+ NButton,
|
|
|
|
|
+ NIcon,
|
|
|
|
|
+ useMessage,
|
|
|
|
|
+ FormInst,
|
|
|
|
|
+ NEl,
|
|
|
|
|
+ NConfigProvider,
|
|
|
|
|
+ GlobalThemeOverrides,
|
|
|
|
|
+ NModal,
|
|
|
|
|
+ NCard
|
|
|
|
|
+} from 'naive-ui'
|
|
|
|
|
+import { User, Lock } from '@vicons/tabler'
|
|
|
|
|
+import { fetchSendVerify } from '../../api'
|
|
|
|
|
+import { useStorage } from '@vueuse/core'
|
|
|
|
|
+import { useAuthStore } from '@/store'
|
|
|
|
|
+
|
|
|
|
|
+const themeOverrides: GlobalThemeOverrides = {
|
|
|
|
|
+ Input: {
|
|
|
|
|
+ heightMedium: '40px'
|
|
|
|
|
+ },
|
|
|
|
|
+ Button: {
|
|
|
|
|
+ heightMedium: '40px'
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+const emit = defineEmits(['success'])
|
|
|
|
|
+const ms = useMessage()
|
|
|
|
|
+const authStore = useAuthStore()
|
|
|
|
|
+const form = ref({
|
|
|
|
|
+ phone: '',
|
|
|
|
|
+ code: ''
|
|
|
|
|
+})
|
|
|
|
|
+const rules = {
|
|
|
|
|
+ phone: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator(rule: any, value: any) {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ return new Error('请输入手机号')
|
|
|
|
|
+ } else if (!/^1[3-9]\d{9}$/.test(value)) {
|
|
|
|
|
+ return new Error('手机号格式错误')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: ['submit']
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ code: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator(rule: any, value: any) {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ return new Error('请输入验证码')
|
|
|
|
|
+ } else if (!/^\d{4}$/.test(value)) {
|
|
|
|
|
+ return new Error('验证码格式错误')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: ['submit']
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+const onlyAllowNumber = (value: string) => !value || /^\d+$/.test(value)
|
|
|
|
|
+const countDown = ref(0)
|
|
|
|
|
+const agree = useStorage('agree', false)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const loginForm: Ref<FormInst | null> = ref(null)
|
|
|
|
|
+const showCpatchaModal = ref(false)
|
|
|
|
|
+const sending = ref(false)
|
|
|
|
|
+const cpatchaData = ref({ token: '', sessionId: '', sig: '' })
|
|
|
|
|
+
|
|
|
|
|
+function checkRule() {
|
|
|
|
|
+ if (!/^1[3-9]\d{9}$/.test(form.value.phone)) {
|
|
|
|
|
+ ms.error('请输入正确的手机号')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!agree.value) {
|
|
|
|
|
+ ms.error('请先阅读并同意用户服务协议和平台隐私协议')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function verifyCaptcha() {
|
|
|
|
|
+ if (!checkRule()) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ showCpatchaModal.value = true
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ initCaptcha()
|
|
|
|
|
+ }, 100)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function sendVerify() {
|
|
|
|
|
+ if (!checkRule()) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ sending.value = true
|
|
|
|
|
+ await fetchSendVerify({
|
|
|
|
|
+ ...cpatchaData.value,
|
|
|
|
|
+ phone: form.value.phone
|
|
|
|
|
+ })
|
|
|
|
|
+ sending.value = false
|
|
|
|
|
+ countDown.value = 60
|
|
|
|
|
+ const timer = setInterval(() => {
|
|
|
|
|
+ countDown.value--
|
|
|
|
|
+ if (countDown.value <= 0) {
|
|
|
|
|
+ clearInterval(timer)
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 1000)
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ sending.value = false
|
|
|
|
|
+ ms.error(e.message)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function submit() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await loginForm.value?.validate()
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!checkRule()) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ await authStore.phoneLogin(form.value.phone, form.value.code)
|
|
|
|
|
+ emit('success')
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ ms.error(e.message)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function initCaptcha() {
|
|
|
|
|
+ var nc_token = ['FFFF0N0000000000A8BD', new Date().getTime(), Math.random()].join(':')
|
|
|
|
|
+ var nc = window.NoCaptcha.init({
|
|
|
|
|
+ renderTo: '#nc',
|
|
|
|
|
+ appkey: 'FFFF0N0000000000A8BD',
|
|
|
|
|
+ scene: 'nc_activity_h5',
|
|
|
|
|
+ token: nc_token,
|
|
|
|
|
+ trans: { key1: 'code200' },
|
|
|
|
|
+ elementID: ['usernameID'],
|
|
|
|
|
+ is_Opt: 0,
|
|
|
|
|
+ language: 'cn',
|
|
|
|
|
+ timeout: 10000,
|
|
|
|
|
+ retryTimes: 5,
|
|
|
|
|
+ errorTimes: 5,
|
|
|
|
|
+ inline: true,
|
|
|
|
|
+ apimap: {
|
|
|
|
|
+ // 'analyze': '//a.com/nocaptcha/analyze.jsonp',
|
|
|
|
|
+ // 'uab_Url': '//aeu.alicdn.com/js/uac/909.js',
|
|
|
|
|
+ },
|
|
|
|
|
+ bannerHidden: false,
|
|
|
|
|
+ initHidden: false,
|
|
|
|
|
+ callback: function (data: any) {
|
|
|
|
|
+ console.log('success', data)
|
|
|
|
|
+ cpatchaData.value = { token: nc_token, sessionId: data.csessionid, sig: data.sig }
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ showCpatchaModal.value = false
|
|
|
|
|
+ }, 300)
|
|
|
|
|
+ sendVerify()
|
|
|
|
|
+ },
|
|
|
|
|
+ error: function (s: any) {}
|
|
|
|
|
+ })
|
|
|
|
|
+ window.NoCaptcha.setEnabled(true)
|
|
|
|
|
+ nc.reset() //请务必确保这里调用一次reset()方法
|
|
|
|
|
+ window.nc = nc
|
|
|
|
|
+ window.NoCaptcha.upLang('cn', {
|
|
|
|
|
+ LOADING: '加载中...', //加载
|
|
|
|
|
+ SLIDER_LABEL: '请向右滑动进行验证', //等待滑动
|
|
|
|
|
+ CHECK_Y: '验证通过', //通过
|
|
|
|
|
+ ERROR_TITLE: '非常抱歉,这出错了...', //拦截
|
|
|
|
|
+ CHECK_N: '验证未通过', //准备唤醒二次验证
|
|
|
|
|
+ OVERLAY_INFORM: '经检测你当前操作环境存在风险,请输入验证码', //二次验证
|
|
|
|
|
+ TIPS_TITLE: '验证码错误,请重新输入' //验证码输错时的提示
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
|
+.input-icon {
|
|
|
|
|
+ color: var(--n-text-color) !important;
|
|
|
|
|
+}
|
|
|
|
|
+.agree {
|
|
|
|
|
+ color: var(--text-color-3);
|
|
|
|
|
+ :deep(.n-checkbox) {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.n-checkbox__label) {
|
|
|
|
|
+ color: var(--text-color-3);
|
|
|
|
|
+ }
|
|
|
|
|
+ .prim {
|
|
|
|
|
+ color: var(--primary-color);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// captcha stylel
|
|
|
|
|
+@captcha-color: #b17dff;
|
|
|
|
|
+:deep(._nc) {
|
|
|
|
|
+ .stage1 {
|
|
|
|
|
+ .slider {
|
|
|
|
|
+ height: 40px !important;
|
|
|
|
|
+ border-radius: var(--border-radius) !important;
|
|
|
|
|
+ box-shadow: none !important;
|
|
|
|
|
+ background-color: rgba(255, 255, 255, 0.1) !important;
|
|
|
|
|
+ .label {
|
|
|
|
|
+ height: 40px !important;
|
|
|
|
|
+ line-height: 40px !important;
|
|
|
|
|
+ font-size: 13px !important;
|
|
|
|
|
+ background: -webkit-gradient(
|
|
|
|
|
+ linear,
|
|
|
|
|
+ left top,
|
|
|
|
|
+ right top,
|
|
|
|
|
+ color-stop(0, shade(#ffffff, 30%)),
|
|
|
|
|
+ color-stop(0.4, shade(#ffffff, 30%)),
|
|
|
|
|
+ color-stop(0.5, white),
|
|
|
|
|
+ color-stop(0.6, shade(#ffffff, 30%)),
|
|
|
|
|
+ color-stop(1, shade(#ffffff, 30%))
|
|
|
|
|
+ );
|
|
|
|
|
+ background-clip: text;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .track div {
|
|
|
|
|
+ border-radius: var(--border-radius) !important;
|
|
|
|
|
+ color: #fff !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .bg-green {
|
|
|
|
|
+ height: 40px !important;
|
|
|
|
|
+ line-height: 40px !important;
|
|
|
|
|
+ background-color: shade(@captcha-color, 40%) !important;
|
|
|
|
|
+ font-size: 13px !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .bg-red {
|
|
|
|
|
+ background-color: #78c430 !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ .button {
|
|
|
|
|
+ height: 40px !important;
|
|
|
|
|
+ line-height: 40px !important;
|
|
|
|
|
+ font-size: 13px !important;
|
|
|
|
|
+ border-radius: 4px !important;
|
|
|
|
|
+ background-color: @captcha-color !important;
|
|
|
|
|
+ color: #fff !important;
|
|
|
|
|
+ .icon {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 52px !important;
|
|
|
|
|
+ height: 40px !important;
|
|
|
|
|
+ left: 0 !important;
|
|
|
|
|
+ right: 0 !important;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ padding-top: 0;
|
|
|
|
|
+ color: var(--text-color-1) !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|