login.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <view class="login-container">
  3. <view class="login-header">
  4. <view class="logo-box">
  5. <text class="logo-text1">审计</text>
  6. <text class="logo-text2">之家</text>
  7. </view>
  8. <view class="login-title">登录审计之家</view>
  9. <view class="login-subtitle">找岗位, XXXXXXXX</view>
  10. </view>
  11. <view class="login-body">
  12. <!-- 动态替换:同意则原样使用获取手机号功能;不同意则纯点击弹窗 -->
  13. <button v-if="isAgree" class="btn-primary" open-type="getPhoneNumber" @getphonenumber="handleDirectLogin">一键快捷登录</button>
  14. <button v-else class="btn-primary" @tap="showAgreementModal = true">一键快捷登录</button>
  15. <view class="agreement-box">
  16. <checkbox-group @change="onAgreeChange">
  17. <label class="checkbox-label">
  18. <checkbox value="agree" :checked="isAgree" color="#007AFF" style="transform:scale(0.7)" />
  19. <text class="agreement-text">我已经阅读并同意</text>
  20. <text class="link-text" @tap.stop="openDocModal('service')">审计之家服务协议</text>
  21. <text class="agreement-text"> 及 </text>
  22. <text class="link-text" @tap.stop="openDocModal('privacy')">隐私政策</text>
  23. </label>
  24. </checkbox-group>
  25. </view>
  26. </view>
  27. <view class="login-footer">
  28. <view class="divider-text">-登录后权益更多-</view>
  29. <view class="icon-grid">
  30. <view class="icon-item">
  31. <view class="icon-circle">
  32. <image src="/static/icons/icon-info.svg" mode="aspectFit" class="icon-img"></image>
  33. </view>
  34. <text class="icon-text">优质信息</text>
  35. </view>
  36. <view class="icon-item">
  37. <view class="icon-circle">
  38. <image src="/static/icons/icon-service.svg" mode="aspectFit" class="icon-img"></image>
  39. </view>
  40. <text class="icon-text">专属客服</text>
  41. </view>
  42. <view class="icon-item">
  43. <view class="icon-circle">
  44. <image src="/static/icons/icon-recommend.svg" mode="aspectFit" class="icon-img"></image>
  45. </view>
  46. <text class="icon-text">曝光推荐</text>
  47. </view>
  48. <view class="icon-item">
  49. <view class="icon-circle">
  50. <image src="/static/icons/icon-contact.svg" mode="aspectFit" class="icon-img"></image>
  51. </view>
  52. <text class="icon-text">快速联系</text>
  53. </view>
  54. </view>
  55. </view>
  56. <!-- 未同意协议时的提示弹窗 -->
  57. <view class="custom-modal" :class="{ 'is-show': showAgreementModal }">
  58. <view class="modal-mask" @tap="closeAgreementModal"></view>
  59. <view class="modal-content">
  60. <view class="modal-header">
  61. <text class="modal-title">提示</text>
  62. </view>
  63. <view class="modal-body">
  64. <view class="modal-text-wrapper">
  65. <text class="modal-text">请先阅读并同意</text>
  66. <text class="link-text" @tap.stop="openDocModal('service')">服务协议</text>
  67. <text class="modal-text"> 及 </text>
  68. <text class="link-text" @tap.stop="openDocModal('privacy')">隐私政策</text>
  69. </view>
  70. </view>
  71. <view class="modal-footer">
  72. <view class="btn-cancel" @tap="closeAgreementModal">不同意</view>
  73. <view class="btn-confirm" style="position: relative;">
  74. 同意
  75. <button open-type="getPhoneNumber" @getphonenumber="handleDirectLoginFromModal" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 10; padding: 0; margin: 0;"></button>
  76. </view>
  77. </view>
  78. </view>
  79. </view>
  80. <!-- 协议正文弹窗 -->
  81. <view class="custom-modal doc-modal" :class="{ 'is-show': showDocModal }">
  82. <view class="modal-mask" @tap="closeDocModal"></view>
  83. <view class="modal-content doc-modal-content">
  84. <view class="modal-header">
  85. <text class="modal-title">{{ docTitle }}</text>
  86. </view>
  87. <scroll-view class="modal-body doc-modal-body" scroll-y>
  88. <rich-text :nodes="docContent"></rich-text>
  89. </scroll-view>
  90. <view class="modal-footer">
  91. <view class="btn-confirm full-width" @tap="closeDocModal">我知道了</view>
  92. </view>
  93. </view>
  94. </view>
  95. </view>
  96. </template>
  97. <script setup lang="js">
  98. import { ref, onMounted } from 'vue';
  99. import { wechatLogin, getAgreement } from '../../api/auth';
  100. const isAgree = ref(false);
  101. const showAgreementModal = ref(false);
  102. const showDocModal = ref(false);
  103. const docTitle = ref('');
  104. const docContent = ref('');
  105. // 保存 wx.login 获取的 code,用于后端校验 openid
  106. const loginCode = ref('');
  107. // 预获取微信登录 code(在页面加载时就获取,避免授权回调时 code 失效)
  108. const getLoginCode = () => {
  109. uni.login({
  110. provider: 'weixin',
  111. success: (res) => {
  112. loginCode.value = res.code;
  113. }
  114. });
  115. };
  116. onMounted(() => {
  117. // 检查是否已有有效 token,如果有则直接跳转首页
  118. const token = uni.getStorageSync('token');
  119. const userInfo = uni.getStorageSync('userInfo');
  120. if (token && userInfo && userInfo.studentId) {
  121. // 已登录,直接跳转
  122. if (userInfo.isNewUser && !userInfo.name) {
  123. uni.redirectTo({ url: '/pages/profile/profile' });
  124. } else {
  125. uni.switchTab({ url: '/pages/jobs/jobs' });
  126. }
  127. return;
  128. }
  129. getLoginCode();
  130. });
  131. // 打开协议正文弹窗(从后端拉取内容)
  132. const openDocModal = async (type) => {
  133. try {
  134. const res = await getAgreement(type);
  135. if (res.code === 200) {
  136. docTitle.value = res.data.title;
  137. docContent.value = res.data.content;
  138. showDocModal.value = true;
  139. return;
  140. }
  141. } catch (err) {
  142. console.error('获取协议失败', err);
  143. uni.showToast({
  144. title: '获取协议失败,请稍后重试',
  145. icon: 'none'
  146. });
  147. }
  148. };
  149. const closeDocModal = () => {
  150. showDocModal.value = false;
  151. };
  152. const onAgreeChange = (e) => {
  153. isAgree.value = e.detail.value.length > 0;
  154. };
  155. const closeAgreementModal = () => {
  156. showAgreementModal.value = false;
  157. };
  158. /**
  159. * 微信授权手机号回调(由于使用了v-if控制按钮,此时必然是同意了)
  160. */
  161. const handleDirectLogin = async (e) => {
  162. if (e.detail.errMsg !== 'getPhoneNumber:ok') {
  163. uni.showToast({
  164. title: '授权失败',
  165. icon: 'none'
  166. });
  167. return;
  168. }
  169. await onAllowLogin(e.detail.code);
  170. };
  171. /**
  172. * 弹窗中的同意并获取手机号回调
  173. */
  174. const handleDirectLoginFromModal = async (e) => {
  175. isAgree.value = true;
  176. showAgreementModal.value = false;
  177. if (e.detail.errMsg !== 'getPhoneNumber:ok') {
  178. uni.showToast({
  179. title: '授权失败',
  180. icon: 'none'
  181. });
  182. return;
  183. }
  184. await onAllowLogin(e.detail.code);
  185. };
  186. /**
  187. * 发起真实登录请求
  188. */
  189. const onAllowLogin = async (phoneCode) => {
  190. uni.showLoading({ title: '登录中...' });
  191. try {
  192. const res = await wechatLogin({
  193. code: loginCode.value,
  194. phoneCode: phoneCode
  195. });
  196. uni.hideLoading();
  197. if (res.code === 200) {
  198. uni.setStorageSync('token', res.data.token);
  199. uni.setStorageSync('userInfo', res.data);
  200. uni.showToast({
  201. title: '登录成功',
  202. icon: 'success'
  203. });
  204. setTimeout(() => {
  205. if (res.data.isNewUser) {
  206. uni.navigateTo({ url: '/pages/profile/profile' });
  207. } else {
  208. uni.switchTab({ url: '/pages/jobs/jobs' });
  209. }
  210. }, 1000);
  211. }
  212. } catch (err) {
  213. console.error('登录异常', err);
  214. uni.hideLoading();
  215. getLoginCode();
  216. }
  217. };
  218. </script>
  219. <style lang="scss" scoped>
  220. @import './login.scss';
  221. // 仅去除 button 默认边框,不覆盖原本 btn-confirm 的高亮背景与字号
  222. .btn-confirm::after {
  223. border: none !important;
  224. }
  225. </style>