login.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <template>
  2. <ie-page bgColor="white">
  3. <ie-navbar title="" :hide-back="hideBack" :placeholder="false" bgColor="transparent" />
  4. <ie-image :is-oss="true" src="/login-bg.png" custom-class="w-full min-h-350 absolute top-0 left-0 z-1" />
  5. <ie-image :is-oss="true" src="/login-title.png" custom-class="w-full h-96 mx-auto mt-240" mode="heightFix" />
  6. <view class="relative z-2 mx-46 mt-178">
  7. <view class="ml-18 flex items-center">
  8. <view class="text-32" :class="{ 'is-active': loginType === 'card' }" @click="changeLoginType('card')">
  9. 会员卡登录
  10. </view>
  11. <view class="w-1 h-30 mx-24 bg-fore-light"></view>
  12. <view class="text-32" :class="{ 'is-active': loginType === 'phone' }" @click="changeLoginType('phone')">
  13. 手机号登录
  14. </view>
  15. </view>
  16. <view class="mt-46">
  17. <view v-show="loginType === 'phone'">
  18. <ie-input custom-class="mt-28" type="number" :maxlength="11" v-model="phone" placeholder="请输入手机号" />
  19. <ie-input custom-class="mt-28" type="number" :maxlength="6" v-model="password" placeholder="请输入验证码">
  20. <ie-sms :phone="phone" :sms-api-type="EnumSmsApiType.NO_VALIDATION_NO_TOKEN" @send="handleSendSuccess" />
  21. </ie-input>
  22. </view>
  23. <view v-show="loginType === 'card'">
  24. <ie-input custom-class="mt-28" type="number" :maxlength="8" v-model="cardNo" placeholder="请输入卡号" />
  25. <ie-input custom-class="mt-28" type="number" :password="!showPassword" :maxlength="6" v-model="cardPassword"
  26. placeholder="请输入密码">
  27. <cover-view class="w-60 h-60 flex items-center justify-center" @click="toggleShowPassword">
  28. <cover-image v-show="!showPassword" src="@/pagesSystem/static/image/icon/icon-eye.png" mode="widthFix"
  29. class="w-44 h-44" />
  30. <cover-image v-show="showPassword" src="@/pagesSystem/static/image/icon/icon-eye-off.png" mode="widthFix"
  31. class="w-44 h-44" />
  32. </cover-view>
  33. </ie-input>
  34. </view>
  35. <view class="mt-42 ml-26 h-28">
  36. <uv-checkbox-group v-if="loginType === 'card'" v-model="rememberPassword">
  37. <uv-checkbox :name="true" label="记住密码" :labelSize="15" :iconSize="14" labelColor="#666666"></uv-checkbox>
  38. </uv-checkbox-group>
  39. </view>
  40. </view>
  41. <view class="mt-84">
  42. <ie-button @click="handleLogin">登录</ie-button>
  43. </view>
  44. <view class="mt-42 ml-26">
  45. <uv-checkbox-group v-model="agreePrivacy">
  46. <uv-checkbox :name="true" shape="circle" label="记住密码" :labelSize="14" :iconSize="13" labelColor="#666666">
  47. <text class="text-28 text-fore-subcontent">已阅读并同意<text class="text-primary"
  48. @click.stop="handleAgreePrivacy('user')">《用户协议》</text>和<text class="text-primary"
  49. @click.stop="handleAgreePrivacy('privacy')">《隐私政策》</text></text>
  50. </uv-checkbox>
  51. </uv-checkbox-group>
  52. </view>
  53. </view>
  54. <ie-captcha ref="captchaRef" v-model:code="code" v-model:uuid="uuid" @valid="handleValid" />
  55. </ie-page>
  56. </template>
  57. <script lang="ts" setup>
  58. import ieCaptcha from '@/components/ie-sms/ie-captcha.vue';
  59. import { useUserStore } from '@/store/userStore';
  60. import { useTransferPage } from '@/hooks/useTransferPage';
  61. import { validatePhone } from '@/hooks/useValidation';
  62. import { useAppConfig } from '@/hooks/useAppConfig';
  63. import { verifyCard } from '@/api/modules/user';
  64. import { EnumBindScene, EnumSmsApiType, EnumUserType } from '@/common/enum';
  65. import { login } from '@/api/modules/login';
  66. import { LoginRequestDTO, MobileLoginResponseDTO, UserInfo } from '@/types/user';
  67. const { transferBack, transferTo, routes } = useTransferPage();
  68. const userStore = useUserStore();
  69. const { isSmsCaptchaEnable } = useAppConfig();
  70. const hideBack = ref(false);
  71. const loginType = ref('card');
  72. const phone = ref('');
  73. const password = ref('');
  74. const cardNo = ref('');
  75. const cardPassword = ref('');
  76. const code = ref('');
  77. const uuid = ref('');
  78. const showPassword = ref(false);
  79. const rememberPassword = ref([false]);
  80. const agreePrivacy = ref([false]);
  81. const changeLoginType = (type: string) => {
  82. loginType.value = type;
  83. }
  84. const handleAgreePrivacy = (type: string) => {
  85. if (type === 'user') {
  86. transferTo(routes.pageUserProtocol, {});
  87. } else if (type === 'privacy') {
  88. transferTo(routes.pagePrivacyPolicy, {});
  89. }
  90. }
  91. const handleSendSuccess = (_phone: string, _code: string, _uuid: string) => {
  92. console.log('短信发送成功', _phone, _code, _uuid);
  93. code.value = _code;
  94. uuid.value = _uuid;
  95. }
  96. const toggleShowPassword = () => {
  97. showPassword.value = !showPassword.value;
  98. }
  99. const loginValidate = () => {
  100. if (loginType.value === 'phone') {
  101. if (!validatePhone(phone.value)) {
  102. uni.$ie.showToast('请输入正确的手机号');
  103. return false;
  104. }
  105. if (!password.value) {
  106. uni.$ie.showToast('请输入验证码');
  107. return false;
  108. }
  109. } else if (loginType.value === 'card') {
  110. if (!cardNo.value || !cardNo.value.trim()) {
  111. uni.$ie.showToast('请输入卡号');
  112. return false;
  113. }
  114. if (!cardPassword.value || !cardPassword.value.trim()) {
  115. uni.$ie.showToast('请输入密码');
  116. return false;
  117. }
  118. }
  119. if (!agreePrivacy.value[0]) {
  120. uni.$ie.showToast('请先阅读并同意用户协议和隐私政策');
  121. return false;
  122. }
  123. return true;
  124. }
  125. const captchaRef = ref();
  126. const handleLogin = async () => {
  127. if (!loginValidate()) {
  128. return;
  129. }
  130. if (loginType.value === 'phone') {
  131. submitLogin();
  132. } else if (loginType.value === 'card') {
  133. if (isSmsCaptchaEnable.value) {
  134. captchaRef.value.open();
  135. } else {
  136. submitLogin();
  137. }
  138. userStore.rememberLoginInfo(!!rememberPassword.value[0], cardNo.value, cardPassword.value);
  139. // submitLogin();
  140. }
  141. }
  142. const submitLogin = async () => {
  143. const params: LoginRequestDTO = {
  144. code: code.value,
  145. uuid: uuid.value
  146. };
  147. if (loginType.value === 'phone') {
  148. params.mobile = phone.value;
  149. params.password = password.value;
  150. handleMobileLogin(params);
  151. } else if (loginType.value === 'card') {
  152. params.username = cardNo.value;
  153. params.password = cardPassword.value;
  154. handleCardLogin(params);
  155. }
  156. }
  157. const handleMobileLogin = async (params: LoginRequestDTO) => {
  158. uni.$ie.showLoading();
  159. login(params).then((res) => {
  160. uni.$ie.hideLoading();
  161. if (res.data) {
  162. const { code, message } = res.data;
  163. if (code === 101) {
  164. console.log('registerInfo:', params)
  165. // 账号不存在,需要注册
  166. transferTo('/pagesSystem/pages/bind-profile/bind-profile', {
  167. data: {
  168. scene: EnumBindScene.REGISTER,
  169. userInfo: {},
  170. cardInfo: {},
  171. registerInfo: {
  172. ...params
  173. }
  174. }
  175. });
  176. }
  177. } else {
  178. if (res.token) {
  179. userStore.login(res.token).then(({ success, userInfo }) => {
  180. if (success) {
  181. transferBack(true);
  182. } else {
  183. uni.$ie.showToast('登录失败')
  184. }
  185. });
  186. }
  187. }
  188. }).catch(err => {
  189. console.log('登录失败', err)
  190. })
  191. }
  192. const handleCardLogin = (params: LoginRequestDTO) => {
  193. uni.$ie.showLoading();
  194. login(params).then(res => {
  195. if (res.token) {
  196. userStore.login(res.token).then(({ success, userInfo }) => {
  197. uni.$ie.hideLoading();
  198. if (success && userInfo) {
  199. const needComplete = needCompleteInfo(userInfo);
  200. if (needComplete) {
  201. transferTo('/pagesSystem/pages/bind-teacher-profile/bind-teacher-profile');
  202. } else {
  203. transferBack(true);
  204. }
  205. } else {
  206. uni.$ie.showToast('登录失败')
  207. }
  208. });
  209. } else if (res.data.code === 101) {
  210. //
  211. verifyCard(cardNo.value, cardPassword.value).then(res => {
  212. console.log('卡信息:', res)
  213. uni.$ie.hideLoading();
  214. if (res.data) {
  215. console.log('registerInfo:', params)
  216. transferTo('/pagesSystem/pages/phone-verify/phone-verify', {
  217. data: {
  218. scene: EnumBindScene.REGISTER,
  219. userInfo: {},
  220. cardInfo: res.data,
  221. registerInfo: params
  222. }
  223. });
  224. }
  225. }).catch(err => {
  226. console.log('验证会员卡失败', err);
  227. })
  228. }
  229. }).catch(err => {
  230. uni.$ie.hideLoading();
  231. console.log('登录失败', err)
  232. });
  233. }
  234. const needCompleteInfo = (userInfo: UserInfo) => {
  235. const { userType, location, examType, endYear } = userInfo;
  236. return [EnumUserType.TEACHER, EnumUserType.AGENT].includes(userType) && (!location || !examType || !endYear);
  237. };
  238. const handleValid = (data: { code: string; uuid: string }) => {
  239. console.log(code.value, uuid.value);
  240. captchaRef.value.close();
  241. submitLogin();
  242. }
  243. onShow(() => {
  244. const pages = getCurrentPages();
  245. hideBack.value = pages.length <= 1;
  246. });
  247. onLoad(() => {
  248. rememberPassword.value[0] = userStore.rememberPwd;
  249. if (import.meta.env.DEV) {
  250. agreePrivacy.value = [true];
  251. phone.value = '17363958504';
  252. password.value = '1234';
  253. }
  254. if (userStore.rememberPwd) {
  255. cardNo.value = userStore.cardNo;
  256. cardPassword.value = userStore.cardPassword;
  257. }
  258. });
  259. </script>
  260. <style lang="scss" scoped>
  261. .is-active {
  262. @apply text-primary font-[800];
  263. }
  264. </style>