Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

315 linhas
6.7 KiB

  1. <template>
  2. <view class="page">
  3. <view class="logo-area">
  4. <image class="logo" src="https://cdn.csybhelp.com/images/cytx/logo.png" mode="aspectFit" />
  5. <text class="title">肠愈同行</text>
  6. <text class="subtitle">患者关爱</text>
  7. </view>
  8. <view class="btn-area">
  9. <!-- #ifdef MP-WEIXIN -->
  10. <button class="login-btn" @tap="handleWxLogin" :loading="loading">
  11. 微信一键登录
  12. </button>
  13. <!-- #endif -->
  14. <!-- #ifdef H5 -->
  15. <view class="phone-form">
  16. <view class="form-item">
  17. <input class="form-input" type="number" v-model="phone" placeholder="请输入手机号" maxlength="11" />
  18. </view>
  19. <view class="form-item code-row">
  20. <input class="form-input code-input" type="number" v-model="smsCode" placeholder="请输入验证码" maxlength="6" />
  21. <button class="sms-btn" :disabled="countdown > 0" @tap="sendSms">
  22. {{ countdown > 0 ? countdown + 's' : '获取验证码' }}
  23. </button>
  24. </view>
  25. <button class="login-btn" @tap="handlePhoneLogin" :loading="loading">
  26. 登录
  27. </button>
  28. </view>
  29. <!-- #endif -->
  30. <!-- #ifdef MP-WEIXIN -->
  31. <view class="agree-row" @tap="agreed = !agreed">
  32. <u-checkbox-group>
  33. <u-checkbox :checked="agreed" shape="circle" activeColor="#0F78E9" size="18" @change="agreed = !agreed" />
  34. </u-checkbox-group>
  35. <text class="agree-text">请阅读并同意</text>
  36. <text class="link" @tap.stop="goService">《用户服务协议》</text>
  37. <text class="agree-text">和</text>
  38. <text class="link" @tap.stop="goPrivacy">《隐私政策》</text>
  39. </view>
  40. <!-- #endif -->
  41. <!-- #ifdef H5 -->
  42. <view class="tip">登录即表示同意
  43. <text class="link" @tap="goPrivacy">《隐私政策》</text>
  44. </view>
  45. <!-- #endif -->
  46. </view>
  47. </view>
  48. </template>
  49. <script setup>
  50. import { ref, onUnmounted } from 'vue'
  51. import { post } from '@/utils/request.js'
  52. import { setToken, setUserInfo } from '@/utils/cache.js'
  53. const loading = ref(false)
  54. const phone = ref('')
  55. const smsCode = ref('')
  56. const countdown = ref(0)
  57. const agreed = ref(false)
  58. let timer = null
  59. onUnmounted(() => {
  60. if (timer) { clearInterval(timer); timer = null }
  61. })
  62. const loginSuccess = (res) => {
  63. setToken(res.data.token)
  64. setUserInfo(res.data.userInfo)
  65. uni.showToast({ title: '登录成功', icon: 'success' })
  66. setTimeout(() => {
  67. const pages = getCurrentPages()
  68. if (pages.length > 1) {
  69. uni.navigateBack()
  70. } else {
  71. uni.switchTab({ url: '/pages/profile/profile' })
  72. }
  73. }, 500)
  74. }
  75. // #ifdef MP-WEIXIN
  76. const handleWxLogin = async () => {
  77. if (loading.value) return
  78. if (!agreed.value) {
  79. return uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
  80. }
  81. loading.value = true
  82. try {
  83. const code = await new Promise((resolve, reject) => {
  84. uni.login({
  85. provider: 'weixin',
  86. success: (res) => resolve(res.code),
  87. fail: () => reject({ msg: '微信登录失败' })
  88. })
  89. })
  90. const res = await post('/api/mp/login', { code })
  91. loginSuccess(res)
  92. } catch (e) {
  93. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  94. } finally {
  95. loading.value = false
  96. }
  97. }
  98. // #endif
  99. // #ifdef H5
  100. const sendSms = async () => {
  101. if (countdown.value > 0) return
  102. if (!phone.value || phone.value.length !== 11) {
  103. return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
  104. }
  105. try {
  106. const res = await post('/api/mp/sendSmsCode', { mobile: phone.value, bizType: 'login' })
  107. if (res.data && res.data.code) {
  108. uni.showToast({ title: `验证码: ${res.data.code}`, icon: 'none', duration: 3000 })
  109. } else {
  110. uni.showToast({ title: '验证码已发送', icon: 'none' })
  111. }
  112. countdown.value = 60
  113. timer = setInterval(() => {
  114. countdown.value--
  115. if (countdown.value <= 0) { clearInterval(timer); timer = null }
  116. }, 1000)
  117. } catch (e) {
  118. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  119. }
  120. }
  121. const handlePhoneLogin = async () => {
  122. if (loading.value) return
  123. if (!phone.value || phone.value.length !== 11) {
  124. return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
  125. }
  126. if (!smsCode.value || smsCode.value.length !== 6) {
  127. return uni.showToast({ title: '请输入6位验证码', icon: 'none' })
  128. }
  129. loading.value = true
  130. try {
  131. const res = await post('/api/mp/phoneLogin', { mobile: phone.value, code: smsCode.value })
  132. loginSuccess(res)
  133. } catch (e) {
  134. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  135. } finally {
  136. loading.value = false
  137. }
  138. }
  139. // #endif
  140. const goPrivacy = () => {
  141. // #ifdef H5
  142. uni.navigateTo({ url: '/pages/content/content?key=privacy_policy_h5' })
  143. // #endif
  144. // #ifdef MP-WEIXIN
  145. uni.navigateTo({ url: '/pages/content/content?key=privacy_policy' })
  146. // #endif
  147. }
  148. const goService = () => {
  149. uni.navigateTo({ url: '/pages/content/content?key=service_policy' })
  150. }
  151. </script>
  152. <style lang="scss" scoped>
  153. .page {
  154. min-height: 100vh;
  155. background: #fff;
  156. display: flex;
  157. flex-direction: column;
  158. align-items: center;
  159. padding-top: 25vh;
  160. /* #ifdef H5 */
  161. padding-top: 10vh;
  162. /* #endif */
  163. }
  164. .logo-area {
  165. display: flex;
  166. flex-direction: column;
  167. align-items: center;
  168. margin-bottom: 80rpx;
  169. .logo {
  170. width: 180rpx;
  171. height: 180rpx;
  172. border-radius: 24rpx;
  173. margin-bottom: 32rpx;
  174. }
  175. .title {
  176. font-size: 44rpx;
  177. font-weight: 600;
  178. color: #303133;
  179. margin-bottom: 8rpx;
  180. }
  181. .subtitle {
  182. font-size: 26rpx;
  183. color: #b0b3b8;
  184. letter-spacing: 2rpx;
  185. }
  186. }
  187. .btn-area {
  188. width: 100%;
  189. padding: 0 64rpx;
  190. box-sizing: border-box;
  191. .login-btn {
  192. width: 100%;
  193. height: 88rpx;
  194. line-height: 88rpx;
  195. background: #0F78E9;
  196. color: #fff;
  197. font-size: 32rpx;
  198. border-radius: 44rpx;
  199. border: none;
  200. &::after {
  201. border: none;
  202. }
  203. &:active {
  204. opacity: 0.85;
  205. }
  206. }
  207. .tip {
  208. text-align: center;
  209. font-size: 24rpx;
  210. color: #909399;
  211. margin-top: 32rpx;
  212. .link {
  213. color: #0F78E9;
  214. }
  215. }
  216. .agree-row {
  217. display: flex;
  218. align-items: center;
  219. justify-content: center;
  220. flex-wrap: wrap;
  221. margin-top: 32rpx;
  222. .agree-text {
  223. font-size: 24rpx;
  224. color: #909399;
  225. margin-left: 8rpx;
  226. }
  227. .link {
  228. font-size: 24rpx;
  229. color: #0F78E9;
  230. }
  231. }
  232. }
  233. .phone-form {
  234. width: 100%;
  235. .form-item {
  236. margin-bottom: 24rpx;
  237. }
  238. .form-input {
  239. width: 100%;
  240. height: 88rpx;
  241. border: 1rpx solid #dcdfe6;
  242. border-radius: 44rpx;
  243. padding: 0 36rpx;
  244. font-size: 30rpx;
  245. box-sizing: border-box;
  246. background: #f8f9fa;
  247. }
  248. .code-row {
  249. display: flex;
  250. gap: 20rpx;
  251. .code-input {
  252. flex: 2;
  253. }
  254. }
  255. .sms-btn {
  256. flex: 1;
  257. height: 88rpx;
  258. line-height: 88rpx;
  259. padding: 0;
  260. background: #0F78E9;
  261. color: #fff;
  262. border: none;
  263. border-radius: 44rpx;
  264. font-size: 26rpx;
  265. white-space: nowrap;
  266. text-align: center;
  267. &::after {
  268. border: none;
  269. }
  270. &[disabled] {
  271. background: #a0cfff;
  272. color: #fff;
  273. }
  274. }
  275. .login-btn {
  276. margin-top: 16rpx;
  277. }
  278. }
  279. </style>