index.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <div>
  3. <v-form @submit.prevent ref="phoneForm">
  4. <!-- 邀请新同事快捷登录时需要填写邮箱 -->
  5. <v-text-field
  6. v-if="props.showEmailInput"
  7. v-model="loginData.email"
  8. placeholder="请输入企业邮箱"
  9. color="primary"
  10. variant="outlined"
  11. density="compact"
  12. validate-on="input"
  13. prepend-inner-icon="mdi-email"
  14. :rules="emailRules"
  15. ></v-text-field>
  16. <v-text-field v-model="loginData.phone" counter="11" :disabled="props.phoneDisabled" :placeholder="$t('login.mobileNumberPlaceholder')" color="primary" variant="outlined" density="compact" :rules="phoneRules" validate-on="input">
  17. <template v-slot:prepend-inner>
  18. <span class="d-flex">
  19. <v-icon icon="mdi-cellphone" size="24" color="#818181"></v-icon>
  20. <!-- <span class="d-flex" id="menu-activator">
  21. <span class="phone-number">{{ currentArea }}</span>
  22. <v-icon size="20">mdi-chevron-down</v-icon>
  23. </span>
  24. <v-menu activator="#menu-activator">
  25. <v-list>
  26. <v-list-item v-for="(item, index) in items" :key="index" :value="index" @click="handleChangeCurrentArea(item)">
  27. <v-list-item-title>{{ item.label }}</v-list-item-title>
  28. </v-list-item>
  29. </v-list>
  30. </v-menu> -->
  31. </span>
  32. </template>
  33. </v-text-field>
  34. <v-text-field
  35. v-model="loginData.code"
  36. :placeholder="$t('login.enterCode')"
  37. color="primary"
  38. variant="outlined"
  39. density="compact"
  40. prepend-inner-icon="mdi-security"
  41. :rules="codeValid"
  42. @keyup.enter="handleEnter"
  43. >
  44. <template #append-inner>
  45. <span v-if="showCode" class="login-code" @click="handleCode">{{ $t('login.getSmsCode') }}</span>
  46. <span v-else class="disable">{{ $t('login.retrieveAgain') }}{{ count }}s</span>
  47. </template>
  48. </v-text-field>
  49. </v-form>
  50. </div>
  51. <Verify
  52. v-if="props.openVerify"
  53. ref="smsVerify"
  54. captchaType="blockPuzzle"
  55. :imgSize="{ width: '400px', height: '200px' }"
  56. mode="pop"
  57. @success="verifySuccess"
  58. />
  59. </template>
  60. <script setup>
  61. defineOptions({ name: 'verification-code' })
  62. import { ref, reactive } from 'vue'
  63. import { setCodeTime } from '@/utils/code'
  64. import { checkEmail } from '@/utils/validate'
  65. import { sendSmsCode } from '@/api/common/index'
  66. import { useI18n } from '@/hooks/web/useI18n'
  67. import Snackbar from '@/plugins/snackbar'
  68. import Verify from '@/components/Verifition'
  69. const emits = defineEmits(['handleEnter', 'verifySuccess'])
  70. const { t } = useI18n()
  71. const props = defineProps({
  72. phoneDisabled: Boolean,
  73. phone: String,
  74. openVerify: { // 是否开启人机验证
  75. type: Boolean,
  76. default: false
  77. },
  78. showEmailInput: { // 需要输入邮箱
  79. type: Boolean,
  80. default: false
  81. },
  82. scene: { // 短信验证码scene参数: 30-手机号登陆 31-修改手机 32-修改密码 33-忘记密码
  83. type: [Number, String],
  84. default: 30
  85. }
  86. })
  87. const phoneRules = ref([
  88. value => {
  89. if (value) return true
  90. return t('login.mobileNumberPlaceholder')
  91. },
  92. value => {
  93. if (value?.length <= 11 && /^1[3456789]\d{9}$/.test(value)) return true
  94. return t('login.correctPhoneNumber')
  95. }
  96. ])
  97. const codeValid = ref([
  98. value => {
  99. if (value) return true
  100. return '请输入验证码'
  101. },
  102. value => {
  103. if (/^\d{6}$/.test(value)) return true
  104. return '请输入正确格式的六位数字验证码'
  105. }
  106. ])
  107. const emailRules = ref([
  108. value => {
  109. if (value) return true
  110. return props.placeholder ? props.placeholder : '请输入企业邮箱'
  111. },
  112. value => {
  113. if (checkEmail(value)) return true
  114. return '请输入正确的企业邮箱'
  115. }
  116. ])
  117. // 手机号区域
  118. // const currentArea = ref('0086')
  119. // const items = [
  120. // { label: '中国大陆-0086', value: '0086' }
  121. // ]
  122. // const handleChangeCurrentArea = (e) => {
  123. // currentArea.value = e.value
  124. // }
  125. // 获取验证码
  126. let stopHandleCode = false // 人机校验重启不自动获取短信
  127. const smsVerify = ref()
  128. const getCode = async (stop = false) => {
  129. stopHandleCode = stop
  130. // 弹出验证码 // 已开启:则展示验证码;只有完成验证码的情况,才进行登录
  131. smsVerify.value.show()
  132. }
  133. const verifySuccess = (params) => {
  134. loginData.captchaVerification = params.captchaVerification
  135. if (!stopHandleCode) handleCode()
  136. else emits('verifySuccess', loginData.captchaVerification)
  137. }
  138. const clearCaptcha = () => {
  139. loginData.captchaVerification = ''
  140. }
  141. // 获取验证码
  142. const showCode = ref(true)
  143. const count = ref(0)
  144. const timer = ref(null)
  145. const handleCode = () => {
  146. if (!loginData.phone) {
  147. Snackbar.warning(t('login.mobileNumberPlaceholder'))
  148. return
  149. }
  150. if (props.openVerify && !loginData.captchaVerification) {
  151. getCode()
  152. return
  153. }
  154. count.value = 60
  155. setTime()
  156. getSmsCode()
  157. }
  158. const getSmsCode = async () => {
  159. const query = {
  160. phone: loginData.phone,
  161. scene: props.scene ? props.scene-0 : 30
  162. }
  163. // try {
  164. await sendSmsCode(query)
  165. Snackbar.success(t('login.sendCode'))
  166. // } catch (error) {
  167. // Snackbar.error(error.msg)
  168. // }
  169. }
  170. const setTime = () => {
  171. showCode.value = false
  172. timer.value = setInterval(() => {
  173. let number = count.value
  174. if (number > 0 && number <= 60) {
  175. count.value--
  176. setCodeTime(number - 1)
  177. } else {
  178. showCode.value = true
  179. clearInterval(timer.value)
  180. timer.value = null
  181. }
  182. }, 1000)
  183. }
  184. const autoTimer = () => {
  185. count.value = 0
  186. if(!count.value) return
  187. setTime()
  188. }
  189. autoTimer()
  190. const loginUserPhone = localStorage.getItem('loginUserPhone') || ''
  191. const loginData = reactive({
  192. email: '',
  193. phone: loginUserPhone,
  194. code: '',
  195. captchaVerification: ''
  196. })
  197. if (props.phone) loginData.phone = props.phone
  198. const phoneForm = ref()
  199. const handleEnter = () => {
  200. emits('handleEnter')
  201. }
  202. // const resetForm = () => {
  203. // loginData.email = ''
  204. // loginData.phone = ''
  205. // loginData.code = ''
  206. // count.value = 0
  207. // }
  208. defineExpose({
  209. // resetForm,
  210. clearCaptcha,
  211. getCode,
  212. loginData,
  213. phoneForm
  214. })
  215. </script>
  216. <style lang="scss" scoped>
  217. .login-code {
  218. width: 97px;
  219. color: var(--v-primary-base);
  220. text-align: end;
  221. font-size: 12px;
  222. cursor: pointer;
  223. }
  224. .disable {
  225. width: 72px;
  226. color: grey;
  227. font-size: 12px;
  228. }
  229. .phone-number {
  230. width: 34px;
  231. font-size: 12px;
  232. }
  233. </style>