login.nvue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <view class="login-page">
  3. <!-- 顶部欢迎语 -->
  4. <view class="header">
  5. <text class="hello">HELLO,</text>
  6. <text class="welcome">欢迎使用研年健康平台</text>
  7. </view>
  8. <!-- 表单区域 -->
  9. <view class="form-area">
  10. <!-- 手机号输入 -->
  11. <view class="input-wrapper">
  12. <image class="input-icon" src="/static/login/phone.png" />
  13. <input
  14. class="input-field"
  15. type="number"
  16. maxlength="11"
  17. placeholder="请输入手机号"
  18. placeholder-class="placeholder-text"
  19. :value="phone"
  20. @input="onPhoneInput"
  21. />
  22. </view>
  23. <!-- 验证码输入 -->
  24. <view class="input-wrapper">
  25. <image class="input-icon" src="/static/login/pwd.png" />
  26. <input
  27. class="input-field code-input"
  28. type="number"
  29. maxlength="6"
  30. placeholder="请输入验证码"
  31. placeholder-class="placeholder-text"
  32. :value="code"
  33. @input="onCodeInput"
  34. />
  35. <text
  36. class="code-btn"
  37. :class="[codeBtnDisabled ? 'code-btn-disabled' : '']"
  38. @click="onGetCode"
  39. >{{ countdown > 0 ? countdown + 's后重发' : '获取验证码' }}</text>
  40. </view>
  41. <!-- 登录按钮 -->
  42. <view
  43. class="login-btn"
  44. :class="[!canLogin ? 'login-btn-disabled' : '']"
  45. @click="onLogin"
  46. >
  47. <text class="login-btn-text">登录</text>
  48. </view>
  49. </view>
  50. <!-- 其他登录方式 -->
  51. <view class="other-login">
  52. <view class="divider">
  53. <view class="divider-line"></view>
  54. <text class="divider-text">其他登录方式</text>
  55. <view class="divider-line"></view>
  56. </view>
  57. <view class="third-party-area">
  58. <!-- 微信登录 -->
  59. <view class="third-party-btn" @click="onWechatLogin">
  60. <image class="third-party-icon" src="/static/login/wechat@2x.png" />
  61. </view>
  62. </view>
  63. <!-- Apple 登录 - 仅iOS显示,符合苹果审核规范 -->
  64. <view class="apple-section" >
  65. <view class="apple-login-btn" @click="onAppleLogin">
  66. <image class="apple-icon" src="/static/login/apple@2x.png" />
  67. <text class="apple-btn-text">通过 Apple 登录</text>
  68. </view>
  69. </view>
  70. </view>
  71. <!-- 底部协议 -->
  72. <view class="footer">
  73. <view class="agreement-row" @click="onToggleAgreement">
  74. <view class="checkbox" :class="[agreed ? 'checkbox-checked' : '']">
  75. <text class="checkbox-icon" v-if="agreed">✓</text>
  76. </view>
  77. <text class="agreement-text">登录即代表同意</text>
  78. <text class="agreement-link" @click.stop="onOpenPrivacy">隐私政策</text>
  79. <text class="agreement-text">和</text>
  80. <text class="agreement-link" @click.stop="onOpenTerms">用户协议</text>
  81. </view>
  82. </view>
  83. <!-- 隐私政策弹窗 -->
  84. <customPopup :isShow="isShowDrawer" @closePop="isShowDrawer=false">
  85. <view class="boxcontent">
  86. <view class="rich_header">
  87. <text class="rich_title">服务协议与隐私政策</text>
  88. </view>
  89. <scroll-view class="privateContent" scroll-y="true">
  90. <view class="contentBox">
  91. <text class="content-text">感谢您信任并使用研年健康平台!我们深知保护个人隐私信息的重要性,请在使用研年健康平台的服务前,认真阅读我们的</text>
  92. <view class="linebox">
  93. <text class="link-text" @click="goWebview(1)">《用户协议》</text>
  94. <text class="content-text">和</text>
  95. <text class="link-text" @click="goWebview(2)">《隐私政策》</text>
  96. </view>
  97. <text class="content-text">,以帮助您了解我们在收集、使用、存储和共享您个人信息的情况以及您享有的相关权利。</text>
  98. <text class="content-text"> 请您仔细阅读并作出适当的选择:</text>
  99. <text class="content-text"> 1.《用户协议》的主要内容包括:协议适用范围、服务内容及形式、软件使用及许可、终端责任安全、用户权利与义务、用户行为规范、知识产权声明、用户信息保护。</text>
  100. <text class="content-text"> 2.关于《隐私政策》,我们特别向您说明:</text>
  101. <text class="content-text">(1)根据合法、正当、必要的原则,我们会收集实现产品功能所必要的信息,包括:您在注册帐号时所填写的信息(昵称、手机号码);您的日志信息及设备信息、设备型号;当您选择使用部分服务时,我们将在征求您的同意后获取相关权限。</text>
  102. </view>
  103. </scroll-view>
  104. <view class="rich_btn">
  105. <text class="btn-disagree" @click="onAgree(0)">不同意</text>
  106. <text class="btn-agree" @click="onAgree(1)">同意</text>
  107. </view>
  108. </view>
  109. </customPopup>
  110. <ay-toast ref="ayToast" />
  111. </view>
  112. </template>
  113. <script>
  114. import customPopup from '@/components/customPopup/customPopup.nvue'
  115. import ayToast from '@/components/ay-toast/ay-toast.nvue'
  116. export default {
  117. components: {
  118. customPopup,
  119. ayToast
  120. },
  121. data() {
  122. return {
  123. phone: '',
  124. code: '',
  125. countdown: 0,
  126. agreed: false,
  127. isIOS: false,
  128. countdownTimer: null,
  129. isShowDrawer: false
  130. }
  131. },
  132. computed: {
  133. isPhoneValid() {
  134. return /^1[3-9]\d{9}$/.test(this.phone)
  135. },
  136. codeBtnDisabled() {
  137. return this.countdown > 0 || !this.isPhoneValid
  138. },
  139. canLogin() {
  140. return this.isPhoneValid && this.code.length >= 4 && this.agreed
  141. }
  142. },
  143. onLoad() {
  144. // 判断平台
  145. const sysInfo = uni.getSystemInfoSync()
  146. this.isIOS = sysInfo.platform === 'ios'
  147. },
  148. onUnload() {
  149. if (this.countdownTimer) {
  150. clearInterval(this.countdownTimer)
  151. this.countdownTimer = null
  152. }
  153. },
  154. methods: {
  155. onPhoneInput(e) {
  156. this.phone = e.detail.value
  157. },
  158. onCodeInput(e) {
  159. this.code = e.detail.value
  160. },
  161. // 获取验证码
  162. onGetCode() {
  163. if (this.codeBtnDisabled) return
  164. if (!this.isPhoneValid) {
  165. this.$toastRef.error('请输入正确的手机号')
  166. return
  167. }
  168. // 开始倒计时
  169. this.countdown = 60
  170. this.countdownTimer = setInterval(() => {
  171. this.countdown--
  172. if (this.countdown <= 0) {
  173. clearInterval(this.countdownTimer)
  174. this.countdownTimer = null
  175. }
  176. }, 1000)
  177. this.$toastRef.success('验证码已发送')
  178. // TODO: 调用发送验证码接口
  179. },
  180. // 登录
  181. onLogin() {
  182. if (!this.agreed) {
  183. this.$toastRef.error('请先同意隐私政策和用户协议')
  184. return
  185. }
  186. if (!this.isPhoneValid) {
  187. this.$toastRef.error('请输入正确的手机号')
  188. return
  189. }
  190. if (this.code.length < 4) {
  191. this.$toastRef.error('请输入验证码')
  192. return
  193. }
  194. this.$toastRef.loading('登录中...')
  195. // TODO: 调用登录接口
  196. // http.post('/auth/login', { phone: this.phone, code: this.code }, { withToken: false })
  197. },
  198. // 微信登录
  199. onWechatLogin() {
  200. if (!this.agreed) {
  201. this.$toastRef.error('请先同意隐私政策和用户协议')
  202. return
  203. }
  204. this.$toastRef.text('微信登录')
  205. // TODO: 调用微信登录
  206. },
  207. // Apple登录
  208. onAppleLogin() {
  209. if (!this.agreed) {
  210. this.$toastRef.error('请先同意隐私政策和用户协议')
  211. return
  212. }
  213. this.$toastRef.text('Apple登录')
  214. // TODO: 调用 Apple 登录
  215. },
  216. // 切换协议勾选
  217. onToggleAgreement() {
  218. this.agreed = !this.agreed
  219. },
  220. // 打开隐私政策/用户协议弹窗
  221. onOpenPrivacy() {
  222. this.isShowDrawer = true
  223. },
  224. onOpenTerms() {
  225. this.isShowDrawer = true
  226. },
  227. // 弹窗中同意/不同意操作
  228. onAgree(type) {
  229. this.isShowDrawer = false
  230. if (type === 1) {
  231. this.agreed = true
  232. }
  233. },
  234. // 跳转webview查看完整协议
  235. goWebview(id) {
  236. uni.navigateTo({
  237. url: '/pages/webview/webview?id=' + id
  238. })
  239. }
  240. }
  241. }
  242. </script>
  243. <style>
  244. .login-page {
  245. flex: 1;
  246. background-color: #ffffff;
  247. padding-left: 40rpx;
  248. padding-right: 40rpx;
  249. padding-top: 160rpx;
  250. }
  251. /* 顶部欢迎语 */
  252. .header {
  253. margin-bottom: 80rpx;
  254. }
  255. .hello {
  256. font-size: 56rpx;
  257. font-weight: bold;
  258. color: #1a9e78;
  259. margin-bottom: 10rpx;
  260. }
  261. .welcome {
  262. font-size: 40rpx;
  263. font-weight: bold;
  264. color: #1a9e78;
  265. }
  266. /* 表单区域 */
  267. .form-area {
  268. margin-bottom: 60rpx;
  269. }
  270. .input-wrapper {
  271. flex-direction: row;
  272. align-items: center;
  273. background-color: #f5f5f5;
  274. border-radius: 16rpx;
  275. padding-left: 24rpx;
  276. padding-right: 24rpx;
  277. height: 100rpx;
  278. margin-bottom: 24rpx;
  279. }
  280. .input-icon {
  281. width: 40rpx;
  282. height: 40rpx;
  283. margin-right: 16rpx;
  284. }
  285. .input-field {
  286. flex: 1;
  287. font-size: 30rpx;
  288. color: #333333;
  289. height: 100rpx;
  290. }
  291. .placeholder-text {
  292. color: #999999;
  293. font-size: 30rpx;
  294. }
  295. .code-input {
  296. flex: 1;
  297. }
  298. .code-btn {
  299. font-size: 28rpx;
  300. color: #1a9e78;
  301. padding-left: 20rpx;
  302. }
  303. .code-btn-disabled {
  304. color: #cccccc;
  305. }
  306. /* 登录按钮 */
  307. .login-btn {
  308. height: 96rpx;
  309. background-color: #7ecdb8;
  310. border-radius: 48rpx;
  311. align-items: center;
  312. justify-content: center;
  313. margin-top: 20rpx;
  314. }
  315. .login-btn-disabled {
  316. background-color: #c8e6dc;
  317. }
  318. .login-btn-text {
  319. font-size: 32rpx;
  320. color: #ffffff;
  321. font-weight: bold;
  322. }
  323. /* 其他登录方式 */
  324. .other-login {
  325. flex: 1;
  326. align-items: center;
  327. }
  328. .divider {
  329. flex-direction: row;
  330. align-items: center;
  331. margin-top: 60rpx;
  332. margin-bottom: 50rpx;
  333. }
  334. .divider-line {
  335. width: 140rpx;
  336. height: 1px;
  337. background-color: #e0e0e0;
  338. }
  339. .divider-text {
  340. font-size: 26rpx;
  341. color: #999999;
  342. margin-left: 20rpx;
  343. margin-right: 20rpx;
  344. }
  345. /* 第三方登录按钮 */
  346. .third-party-area {
  347. flex-direction: row;
  348. justify-content: center;
  349. align-items: center;
  350. margin-bottom: 40rpx;
  351. }
  352. .third-party-btn {
  353. width: 100rpx;
  354. height: 100rpx;
  355. border-radius: 50rpx;
  356. background-color: #f5f5f5;
  357. align-items: center;
  358. justify-content: center;
  359. }
  360. .third-party-icon {
  361. width: 56rpx;
  362. height: 56rpx;
  363. }
  364. /* Apple 登录按钮 - 符合苹果HIG规范 */
  365. .apple-section {
  366. align-items: center;
  367. margin-top: 20rpx;
  368. }
  369. .apple-login-btn {
  370. flex-direction: row;
  371. align-items: center;
  372. justify-content: center;
  373. width: 580rpx;
  374. height: 88rpx;
  375. background-color: #000000;
  376. border-radius: 44rpx;
  377. }
  378. .apple-icon {
  379. width: 36rpx;
  380. height: 36rpx;
  381. margin-right: 12rpx;
  382. }
  383. .apple-btn-text {
  384. font-size: 30rpx;
  385. color: #ffffff;
  386. font-weight: 500;
  387. }
  388. /* 底部协议 */
  389. .footer {
  390. position: fixed;
  391. bottom: 60rpx;
  392. left: 40rpx;
  393. right: 40rpx;
  394. }
  395. .agreement-row {
  396. flex-direction: row;
  397. align-items: center;
  398. justify-content: center;
  399. }
  400. .checkbox {
  401. width: 32rpx;
  402. height: 32rpx;
  403. border-radius: 16rpx;
  404. border-width: 2rpx;
  405. border-style: solid;
  406. border-color: #cccccc;
  407. align-items: center;
  408. justify-content: center;
  409. margin-right: 10rpx;
  410. }
  411. .checkbox-checked {
  412. background-color: #1a9e78;
  413. border-color: #1a9e78;
  414. }
  415. .checkbox-icon {
  416. font-size: 20rpx;
  417. color: #ffffff;
  418. }
  419. .agreement-text {
  420. font-size: 24rpx;
  421. color: #999999;
  422. }
  423. .agreement-link {
  424. font-size: 24rpx;
  425. color: #1a9e78;
  426. }
  427. /* 隐私政策弹窗 */
  428. .boxcontent {
  429. padding-left: 10rpx;
  430. padding-right: 10rpx;
  431. }
  432. .rich_header {
  433. align-items: center;
  434. margin-bottom: 30rpx;
  435. }
  436. .rich_title {
  437. font-size: 34rpx;
  438. font-weight: bold;
  439. color: #333333;
  440. }
  441. .privateContent {
  442. height: 500rpx;
  443. }
  444. .contentBox {
  445. padding-left: 10rpx;
  446. padding-right: 10rpx;
  447. }
  448. .content-text {
  449. font-size: 26rpx;
  450. color: #666666;
  451. line-height: 44rpx;
  452. }
  453. .linebox {
  454. flex-direction: row;
  455. flex-wrap: wrap;
  456. align-items: center;
  457. }
  458. .link-text {
  459. font-size: 26rpx;
  460. color: #1a9e78;
  461. line-height: 44rpx;
  462. }
  463. .rich_btn {
  464. flex-direction: row;
  465. justify-content: space-between;
  466. margin-top: 30rpx;
  467. padding-left: 20rpx;
  468. padding-right: 20rpx;
  469. }
  470. .btn-disagree {
  471. flex: 1;
  472. height: 80rpx;
  473. line-height: 80rpx;
  474. text-align: center;
  475. font-size: 30rpx;
  476. color: #999999;
  477. background-color: #f5f5f5;
  478. border-radius: 40rpx;
  479. margin-right: 20rpx;
  480. }
  481. .btn-agree {
  482. flex: 1;
  483. height: 80rpx;
  484. line-height: 80rpx;
  485. text-align: center;
  486. font-size: 30rpx;
  487. color: #ffffff;
  488. background-color: #1a9e78;
  489. border-radius: 40rpx;
  490. margin-left: 20rpx;
  491. }
  492. </style>