No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 

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