login.uvue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. <template>
  2. <view class="page-container">
  3. <view class="close-btn" @click="goBack">
  4. <uni-icons type="closeempty" size="24" color="#333">
  5. </uni-icons>
  6. </view>
  7. <view class="header-section">
  8. <text class="hello-text">
  9. Hello!
  10. </text>
  11. <text class="welcome-text">
  12. 欢迎来到小丁到家
  13. </text>
  14. <image src="/static/logo-massage.png" class="logo-img" mode="aspectFit">
  15. </image>
  16. </view>
  17. <view class="form-section">
  18. <view class="input-box">
  19. <input class="input-field" type="number" placeholder="请输入手机号码" v-model="phone" maxlength="11" />
  20. </view>
  21. <view class="input-box row-between">
  22. <input class="input-field" type="number" placeholder="请输入验证码" v-model="code" maxlength="6" />
  23. <text class="code-btn" :class="{ disabled: !canSend }" @click="sendCode">
  24. {{ codeText }}
  25. </text>
  26. </view>
  27. <view class="login-btn" :class="{ disabled: !canLogin }" @click="doLogin">
  28. 登录
  29. </view>
  30. <view class="agree-row row-start">
  31. <checkbox :checked="isAgree" @change="onAgreeChange" style="transform: scale(0.8);" />
  32. <view class="agree-content">
  33. <text class="agree-text">
  34. 我已阅读并同意
  35. <text class="link-text">
  36. 《用户协议》
  37. </text>
  38. <text class="link-text">
  39. 《隐私政策》
  40. </text>
  41. <text class="link-text">
  42. 《上门按摩服务行业平台公约》
  43. </text>
  44. ,未注册的手机号将自动创建小丁到家账号
  45. </text>
  46. </view>
  47. </view>
  48. </view>
  49. <view class="footer-section">
  50. <view class="divider-row row-center">
  51. <view class="line">
  52. </view>
  53. <text class="divider-text">
  54. 其他登录方式
  55. </text>
  56. <view class="line">
  57. </view>
  58. </view>
  59. <view class="methods-row row-center">
  60. <view class="method-item column-center" @click="oneKeyNav">
  61. <view class="icon-box blue-bg">
  62. <uni-icons type="checkmark" size="20" color="#fff">
  63. </uni-icons>
  64. </view>
  65. <text class="method-name">
  66. 一键登录
  67. </text>
  68. </view>
  69. <view class="method-item column-center" @click="wechatLogin">
  70. <view class="icon-box green-bg">
  71. <uni-icons type="weixin" size="20" color="#fff"></uni-icons>
  72. </view>
  73. <text class="method-name">
  74. 微信登录
  75. </text>
  76. </view>
  77. </view>
  78. </view>
  79. <view v-if="showModal" class="modal-mask" @click="showModal=false">
  80. <view class="modal-box" @click.stop>
  81. <text class="modal-title">
  82. 服务协议及隐私政策
  83. </text>
  84. <scroll-view scroll-y class="modal-scroll">
  85. <text class="modal-welcome">
  86. 欢迎您使用小丁到家!
  87. </text>
  88. <text class="modal-desc">
  89. 请你务必审慎阅读、并充分理解
  90. <text class="modal-link">
  91. 《用户协议》
  92. </text>
  93. <text class="modal-link">
  94. 《隐私政策》
  95. </text>
  96. ,协议内容包括但不限于:
  97. </text>
  98. <text class="modal-list">
  99. 1、在您使用软件及服务的过程中,向您提供相关基本功能,我们将根据合法、正当、必要的原则,收集或使用必要的个人信息;
  100. </text>
  101. <text class="modal-list">
  102. 2、基于您的授权,我们可能会获取您的地理位置、相册、相机等相关软件权限;
  103. </text>
  104. <text class="modal-list">
  105. 3、我们会采取符合标准的技术措施和数据安全措施来保护您的个人信息安全;
  106. </text>
  107. <text class="modal-list">
  108. 4、您可以查询,更正,管理您的个人信息,我们也提供账户注销的渠道;
  109. </text>
  110. <text class="modal-footer">
  111. 如您同意以上协议内容,请点击“同意”开始使用我们的产品和服务,我们依法尽全力保护您的个人信息。
  112. </text>
  113. </scroll-view>
  114. <view class="modal-btns row-between">
  115. <text class="modal-btn reject" @click="rejectAgreement">
  116. 拒绝并退出
  117. </text>
  118. <text class="modal-btn agree" @click="agreeAgreement">
  119. 同意
  120. </text>
  121. </view>
  122. </view>
  123. </view>
  124. </view>
  125. </template>
  126. <script setup lang="uts">
  127. import { ref, computed } from 'vue';
  128. const phone = ref<string>('');
  129. const code = ref<string>('');
  130. const isAgree = ref<boolean>(false);
  131. const showModal = ref<boolean>(false);
  132. const countdown = ref<number>(0);
  133. // 定义定时器变量
  134. let timer : number | null = null;
  135. const canSend = computed(() => phone.value.length === 11 && countdown.value === 0);
  136. const canLogin = computed(() => phone.value.length === 11 && code.value.length === 6 && isAgree.value);
  137. const codeText = computed(() => countdown.value > 0 ? `${countdown.value}s 后重试` : '获取验证码');
  138. const goBack = () => uni.navigateBack();
  139. const oneKeyNav = () => uni.navigateTo({ url: '/pages/login/login-one-key' });
  140. const wechatLogin = () => uni.showToast({ title: '微信登录开发中', icon: 'none' });
  141. const sendCode = () => {
  142. if (!canSend.value) return;
  143. // ✅ 修复核心:先取值到局部常量,再操作
  144. const currentTimer = timer;
  145. if (currentTimer !== null) {
  146. clearInterval(currentTimer);
  147. }
  148. timer = null;
  149. countdown.value = 60;
  150. uni.showToast({ title: '验证码已发送', icon: 'success' });
  151. timer = setInterval(() => {
  152. countdown.value--;
  153. if (countdown.value <= 0) {
  154. // ✅ 同样在闭包内先取局部常量
  155. const t = timer;
  156. if (t !== null) {
  157. clearInterval(t);
  158. }
  159. timer = null;
  160. countdown.value = 0;
  161. }
  162. }, 1000);
  163. };
  164. const onAgreeChange = (e : any) => {
  165. // UTS 中 any 类型必须 as 成具体类型再访问属性
  166. const evt = e as UTSJSONObject;
  167. if (evt != null) {
  168. const detail = evt["detail"] as UTSJSONObject | null;
  169. if (detail != null) {
  170. const value = detail["value"] as Boolean | null;
  171. isAgree.value = (value != null) && value;
  172. return;
  173. }
  174. }
  175. isAgree.value = false;
  176. };
  177. const doLogin = () => {
  178. if (!canLogin.value) {
  179. uni.showToast({ title: isAgree.value ? '请填写完整信息' : '请先同意协议', icon: 'none' });
  180. return;
  181. }
  182. uni.showLoading({ title: '登录中...' });
  183. setTimeout(() => {
  184. uni.hideLoading();
  185. uni.showToast({ title: '登录成功', icon: 'success' });
  186. }, 1500);
  187. };
  188. const rejectAgreement = () => {
  189. showModal.value = false;
  190. uni.showToast({ title: '您拒绝了协议', icon: 'none' });
  191. };
  192. const agreeAgreement = () => {
  193. showModal.value = false;
  194. isAgree.value = true;
  195. uni.showToast({ title: '已同意协议', icon: 'success' });
  196. };
  197. </script>
  198. <style>
  199. .page-container {
  200. background: linear-gradient(180deg, #e0f7fa 0%, #fff8e1 100%);
  201. height: 100%;
  202. padding: 40rpx 30rpx;
  203. box-sizing: border-box;
  204. }
  205. .close-btn {
  206. position: absolute;
  207. top: 40rpx;
  208. right: 30rpx;
  209. z-index: 10;
  210. }
  211. .header-section {
  212. align-items: center;
  213. margin-bottom: 60rpx;
  214. }
  215. .hello-text {
  216. font-size: 48rpx;
  217. font-weight: bold;
  218. color: #333;
  219. margin-bottom: 10rpx;
  220. }
  221. .welcome-text {
  222. font-size: 32rpx;
  223. color: #666;
  224. margin-bottom: 30rpx;
  225. }
  226. .logo-img {
  227. width: 300rpx;
  228. height: 300rpx;
  229. border-radius: 150rpx;
  230. background-color: rgba(255, 255, 255, 0.3);
  231. }
  232. .form-section {
  233. margin-bottom: 60rpx;
  234. }
  235. .input-box {
  236. background-color: #ffffff;
  237. border-radius: 30rpx;
  238. padding: 20rpx 30rpx;
  239. margin-bottom: 30rpx;
  240. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  241. }
  242. .input-field {
  243. flex: 1;
  244. font-size: 28rpx;
  245. color: #333;
  246. }
  247. .code-btn {
  248. font-size: 26rpx;
  249. color: #ffc107;
  250. font-weight: bold;
  251. white-space: nowrap;
  252. }
  253. .code-btn.disabled {
  254. color: #ccc;
  255. }
  256. .login-btn {
  257. background: linear-gradient(90deg, #ffc107, #ffca2c);
  258. color: #ffffff;
  259. font-size: 32rpx;
  260. font-weight: bold;
  261. text-align: center;
  262. padding: 24rpx 0;
  263. border-radius: 30rpx;
  264. margin-bottom: 30rpx;
  265. box-shadow: 0 4rpx 12rpx rgba(255, 193, 7, 0.3);
  266. }
  267. .login-btn.disabled {
  268. background: #ddd;
  269. color: #999;
  270. box-shadow: none;
  271. }
  272. .agree-row {
  273. align-items: flex-start;
  274. }
  275. .agree-content {
  276. flex: 1;
  277. margin-left: 10rpx;
  278. }
  279. .agree-text {
  280. font-size: 24rpx;
  281. color: #999;
  282. line-height: 1.6;
  283. }
  284. .link-text {
  285. color: #00b894;
  286. border-bottom: 1rpx solid #00b894;
  287. }
  288. .footer-section {
  289. margin-top: auto;
  290. }
  291. .divider-row {
  292. margin-bottom: 40rpx;
  293. align-items: center;
  294. }
  295. .line {
  296. flex: 1;
  297. height: 1rpx;
  298. background-color: #ddd;
  299. }
  300. .divider-text {
  301. font-size: 26rpx;
  302. color: #999;
  303. margin: 0 20rpx;
  304. }
  305. .methods-row {
  306. justify-content: center;
  307. margin-bottom: 40rpx;
  308. }
  309. .method-item {
  310. margin-right: 60rpx;
  311. align-items: center;
  312. }
  313. .method-item:last-child {
  314. margin-right: 0;
  315. }
  316. .icon-box {
  317. width: 80rpx;
  318. height: 80rpx;
  319. border-radius: 40rpx;
  320. justify-content: center;
  321. align-items: center;
  322. margin-bottom: 15rpx;
  323. }
  324. .blue-bg {
  325. background: linear-gradient(135deg, #4a90e2, #67b26f);
  326. }
  327. .green-bg {
  328. background: linear-gradient(135deg, #00b894, #00cec9);
  329. }
  330. .method-name {
  331. font-size: 26rpx;
  332. color: #666;
  333. }
  334. .modal-mask {
  335. position: fixed;
  336. top: 0;
  337. left: 0;
  338. right: 0;
  339. bottom: 0;
  340. background-color: rgba(0, 0, 0, 0.5);
  341. justify-content: center;
  342. align-items: center;
  343. z-index: 100;
  344. }
  345. .modal-box {
  346. background-color: #ffffff;
  347. border-radius: 20rpx;
  348. padding: 40rpx 30rpx;
  349. width: 600rpx;
  350. max-height: 800rpx;
  351. flex-direction: column;
  352. }
  353. .modal-title {
  354. font-size: 36rpx;
  355. font-weight: bold;
  356. color: #333;
  357. text-align: center;
  358. margin-bottom: 20rpx;
  359. }
  360. .modal-scroll {
  361. flex: 1;
  362. margin-bottom: 20rpx;
  363. }
  364. .modal-welcome {
  365. font-size: 28rpx;
  366. color: #333;
  367. margin-bottom: 15rpx;
  368. }
  369. .modal-desc {
  370. font-size: 26rpx;
  371. color: #666;
  372. line-height: 1.6;
  373. margin-bottom: 15rpx;
  374. }
  375. .modal-link {
  376. color: #00b894;
  377. border-bottom: 1rpx solid #00b894;
  378. }
  379. .modal-list {
  380. font-size: 26rpx;
  381. color: #666;
  382. line-height: 1.6;
  383. margin-bottom: 10rpx;
  384. }
  385. .modal-footer {
  386. font-size: 26rpx;
  387. color: #666;
  388. line-height: 1.6;
  389. margin-bottom: 10rpx;
  390. }
  391. .modal-btns {
  392. border-top: 1rpx solid #eee;
  393. padding-top: 20rpx;
  394. }
  395. .modal-btn {
  396. flex: 1;
  397. text-align: center;
  398. font-size: 28rpx;
  399. font-weight: bold;
  400. padding: 15rpx 0;
  401. }
  402. .modal-btn.reject {
  403. color: #999;
  404. margin-right: 20rpx;
  405. }
  406. .modal-btn.agree {
  407. color: #00b894;
  408. }
  409. .row-between {
  410. flex-direction: row;
  411. justify-content: space-between;
  412. align-items: center;
  413. }
  414. .row-start {
  415. flex-direction: row;
  416. align-items: flex-start;
  417. }
  418. .row-center {
  419. flex-direction: row;
  420. justify-content: center;
  421. align-items: center;
  422. }
  423. .column-center {
  424. flex-direction: column;
  425. justify-content: center;
  426. align-items: center;
  427. }
  428. </style>