You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

907 lines
19 KiB

1 month ago
1 month ago
  1. <template>
  2. <view class="login-registration-container">
  3. <!-- 自定义导航栏 -->
  4. <view
  5. class="custom-navbar"
  6. :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
  7. >
  8. <!-- <view class="nav-left">
  9. <text class="back-btn" @click="goToIndex"></text>
  10. </view> -->
  11. <view class="nav-right">
  12. <image
  13. class="icons"
  14. src="../../../static/icons/Headset.png"
  15. alt="联系客服"
  16. />
  17. <image
  18. class="icons"
  19. @click="goToIndex"
  20. src="../../../static/icons/Frame.png"
  21. alt="返回首页"
  22. />
  23. </view>
  24. </view>
  25. <!-- Logo -->
  26. <!-- <image class="logo" src="/static/logo.png" mode="aspectFit"></image> -->
  27. <!-- 欢迎语 -->
  28. <text class="welcome-text">欢迎来到DeepChart</text>
  29. <!-- 邮箱/手机号切换 -->
  30. <view class="switch-container">
  31. <text
  32. class="switch-item"
  33. :class="{ active: switchType === 'Email' }"
  34. @click="switchEmail"
  35. >邮箱</text
  36. >
  37. <text
  38. class="switch-item"
  39. :class="{ active: switchType === 'Phone' }"
  40. @click="switchPhone"
  41. >手机号</text
  42. >
  43. </view>
  44. <!-- 输入框 -->
  45. <view class="input-container">
  46. <view v-if="switchType === 'Email'">
  47. <!-- 修改邮箱输入框容器将图标包含在内 -->
  48. <view class="input-with-icon">
  49. <image
  50. class="input-icon"
  51. src="../../../static/icons/Mail.png"
  52. alt=""
  53. />
  54. <input
  55. class="input-field"
  56. type="text"
  57. placeholder="请输入邮箱"
  58. v-model="email"
  59. />
  60. <view>
  61. <button
  62. class="send-code-btn-email"
  63. :disabled="isCodeBtnDisabled"
  64. :class="{ 'send-code-btn-disabled': isCodeBtnDisabled }"
  65. @click="sendCode"
  66. >
  67. <text
  68. class="send-code-text"
  69. :class="{ 'send-code-btn-disabled-text': isCodeBtnDisabled }"
  70. >{{ codeBtnText }}</text
  71. >
  72. </button>
  73. </view>
  74. </view>
  75. <view class="input-with-icon">
  76. <image
  77. class="input-icon"
  78. src="../../../static/icons/Text-recognition.png"
  79. alt=""
  80. />
  81. <input
  82. class="input-field"
  83. type="text"
  84. placeholder="请输入验证码"
  85. v-model="password"
  86. />
  87. </view>
  88. </view>
  89. <view v-if="switchType === 'Phone'" class="phone-input-container">
  90. <view class="country-code-selector" @click="showCountryPicker">
  91. <image
  92. class="country-flag-img"
  93. src="../../../static/icons/Iphone.png"
  94. mode="aspectFit"
  95. ></image>
  96. <text class="country-code">{{ selectedCountry.code }}</text>
  97. <!-- <text class="arrow-down"></text> -->
  98. </view>
  99. <input
  100. class="input-field phone-input"
  101. type="number"
  102. placeholder="输入手机号"
  103. v-model="phone"
  104. @input="onPhoneInput"
  105. />
  106. <view>
  107. <button
  108. class="send-code-btn"
  109. :disabled="isCodeBtnDisabled"
  110. :class="{ 'send-code-btn-disabled': isCodeBtnDisabled }"
  111. @click="sendCode"
  112. >
  113. <text
  114. class="send-code-text"
  115. :class="{ 'send-code-btn-disabled-text': isCodeBtnDisabled }"
  116. >{{ codeBtnText }}</text
  117. >
  118. </button>
  119. </view>
  120. </view>
  121. <view v-if="switchType === 'Phone'" class="input-with-icon">
  122. <image
  123. class="input-icon"
  124. src="../../../static/icons/Text-recognition.png"
  125. alt=""
  126. />
  127. <input
  128. class="input-field"
  129. type="text"
  130. placeholder="请输入验证码"
  131. v-model="password"
  132. />
  133. </view>
  134. </view>
  135. <!-- 用户协议 -->
  136. <view @click="changeCheckbox" class="agreement-container-one">
  137. <image class="checkbox" :src="checkboxUrl"></image>
  138. <text class="agreement-text-one"
  139. >接受 <text class="link" @click="openAgreement">用户协议</text>
  140. <text class="link" @click="openPrivacy">隐私政策</text></text
  141. >
  142. </view>
  143. <!-- 注册按钮 -->
  144. <button class="register-btn" @click="register">注册</button>
  145. <!-- 或者 -->
  146. <text class="or-text" @click="goToLogin">已有账号登录 </text>
  147. <!-- 同意弹窗 -->
  148. <uniPopup ref="agreementPopup" type="dialog">
  149. <view class="popup-content">
  150. <text class="popup-title">同意并继续</text>
  151. <text class="popup-message">请阅读并同意服务协议和隐私权限</text>
  152. <button class="agree-button" @click="handleAgree">
  153. <text class="agree-text">同意</text>
  154. </button>
  155. </view>
  156. </uniPopup>
  157. <footerBar class="static-footer" :type="type"></footerBar>
  158. </view>
  159. </template>
  160. <script setup>
  161. import { ref } from "vue";
  162. // 导入完整的国家列表
  163. import countryList from "../login/list";
  164. import footerBar from "../../../components/footerBar-cn.vue";
  165. import uniPopup from "../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
  166. import { verificationPhone, verificationEmail } from "../login/verification";
  167. const type = ref("member");
  168. const email = ref("");
  169. const password = ref("");
  170. const phone = ref("");
  171. const country = ref("+86");
  172. const agreed = ref(false);
  173. const switchType = ref("Phone"); // 默认是邮箱注册
  174. const { safeAreaInsets } = uni.getSystemInfoSync();
  175. const codeBtnText = ref("获取验证码");
  176. const isCodeBtnDisabled = ref(false); // 添加验证码按钮禁用状态
  177. const checkboxUrl = ref("../../../static/icons/Check-one-false.png");
  178. // 使用从list.js导入的完整国家列表数据
  179. const countries = ref(
  180. countryList.list.map((item) => ({
  181. name: item.name,
  182. code: `+${item.tel}`,
  183. flag: item.flag,
  184. short: item.short,
  185. en: item.en,
  186. }))
  187. );
  188. // 默认选中的国家(中国)
  189. const selectedCountry = ref(
  190. countries.value.find((country) => country.short === "CN") ||
  191. countries.value[0]
  192. );
  193. // 显示国家选择器
  194. function showCountryPicker() {
  195. uni.showActionSheet({
  196. itemList: countries.value.map(
  197. (country) => `${country.name} ${country.code}`
  198. ),
  199. success: function (res) {
  200. selectedCountry.value = countries.value[res.tapIndex];
  201. },
  202. });
  203. }
  204. function goToIndex() {
  205. // 返回上一页
  206. uni.navigateTo({
  207. url: "/pages/start/index/index",
  208. });
  209. }
  210. function switchEmail() {
  211. // 切换到邮箱注册
  212. switchType.value = "Email";
  213. }
  214. function switchPhone() {
  215. // 切换到手机注册
  216. switchType.value = "Phone";
  217. }
  218. // function register() {
  219. // if (switchType.value === "Email") {
  220. // // 登录逻辑
  221. // if (!email.value) {
  222. // uni.showToast({
  223. // title: "请输入邮箱地址",
  224. // icon: "none",
  225. // });
  226. // return;
  227. // }
  228. // // 发送登录请求
  229. // console.log("登录:", email.value);
  230. // }
  231. // if (switchType.value === "Phone") {
  232. // // 登录逻辑
  233. // if (!phone.value) {
  234. // uni.showToast({
  235. // title: "请输入手机号码",
  236. // icon: "none",
  237. // });
  238. // return;
  239. // }
  240. // // 发送登录请求
  241. // console.log("登录:", phone.value);
  242. // }
  243. // }
  244. function register() {
  245. if (switchType.value === "Phone") {
  246. // 登录逻辑
  247. if (!phone.value) {
  248. uni.showToast({
  249. title: "请输入手机号码",
  250. icon: "none",
  251. });
  252. return;
  253. }
  254. if (!password.value) {
  255. uni.showToast({
  256. title: "请输入验证码",
  257. icon: "none",
  258. });
  259. return;
  260. }
  261. const phoneAll = `${country.value}${phone.value}`;
  262. console.log("完整手机号" + phoneAll);
  263. if (validatePhoneNumber(country.value, phone.value)) {
  264. console.log("登录成功:", phoneAll);
  265. }
  266. // 发送登录请求
  267. // console.log("登录:", phone.value);
  268. }
  269. if (switchType.value === "Email") {
  270. // 登录逻辑
  271. if (!email.value) {
  272. uni.showToast({
  273. title: "请输入邮箱地址",
  274. icon: "none",
  275. });
  276. return;
  277. }
  278. if (!password.value) {
  279. uni.showToast({
  280. title: "请输入验证码",
  281. icon: "none",
  282. });
  283. return;
  284. }
  285. const bool = verificationEmail(email.value);
  286. console.log("验证是否成功", bool);
  287. // 检查格式是否正确
  288. if (!bool) {
  289. uni.showToast({
  290. title: "邮箱格式不正确",
  291. icon: "none",
  292. });
  293. return;
  294. }
  295. // 发送登录请求
  296. console.log("登录:", email.value);
  297. }
  298. if (!agreed.value) {
  299. // 显示同意弹窗
  300. agreementPopup.value.open();
  301. return;
  302. }
  303. // 如果已经同意,则继续登录流程
  304. // uni.showToast({
  305. // title: "登录成功",
  306. // icon: "success",
  307. // });
  308. }
  309. function goToLogin() {
  310. // 跳转到登录页
  311. uni.navigateTo({
  312. url: "/pages/start/login/login",
  313. });
  314. }
  315. function onPhoneInput(e) {
  316. // 确保只允许输入数字
  317. const value = e.detail.value;
  318. // 使用 isNaN 检查是否为有效数字
  319. if (isNaN(value)) {
  320. phone.value = "";
  321. } else {
  322. phone.value = value;
  323. }
  324. }
  325. // function sendCode() {
  326. // // 如果按钮已禁用,则不执行后续逻辑
  327. // if (isCodeBtnDisabled.value) return;
  328. // // 设置按钮为禁用状态
  329. // isCodeBtnDisabled.value = true;
  330. // codeBtnText.value = "重新发送";
  331. // let time = 6;
  332. // const timer = setInterval(() => {
  333. // time--;
  334. // codeBtnText.value = "重新发送 " + time + "S";
  335. // if (time <= 0) {
  336. // clearInterval(timer);
  337. // codeBtnText.value = "重新发送";
  338. // // 倒计时结束后启用按钮
  339. // isCodeBtnDisabled.value = false;
  340. // }
  341. // }, 1000);
  342. // return;
  343. // }
  344. function sendCode() {
  345. if (switchType.value === "Phone") {
  346. if (!phone.value) {
  347. uni.showToast({
  348. title: "请输入手机号",
  349. icon: "none",
  350. });
  351. return;
  352. }
  353. const bool = verificationPhone(country.value, phone.value);
  354. console.log("验证是否成功", bool);
  355. // 检查格式是否正确
  356. if (!bool) {
  357. uni.showToast({
  358. title: "手机号格式不正确",
  359. icon: "none",
  360. });
  361. return;
  362. }
  363. }
  364. if (switchType.value === "Email") {
  365. if (!email.value) {
  366. uni.showToast({
  367. title: "请输入邮箱地址",
  368. icon: "none",
  369. });
  370. return;
  371. }
  372. const bool = verificationEmail(email.value);
  373. console.log("验证是否成功", bool);
  374. // 检查格式是否正确
  375. if (!bool) {
  376. uni.showToast({
  377. title: "邮箱格式不正确",
  378. icon: "none",
  379. });
  380. return;
  381. }
  382. }
  383. // 如果按钮已禁用,则不执行后续逻辑
  384. if (isCodeBtnDisabled.value) return;
  385. // 设置按钮为禁用状态
  386. isCodeBtnDisabled.value = true;
  387. codeBtnText.value = "重新发送";
  388. let time = 6;
  389. const timer = setInterval(() => {
  390. time--;
  391. codeBtnText.value = "重新发送 " + time + "s";
  392. if (time <= 0) {
  393. clearInterval(timer);
  394. codeBtnText.value = "重新发送";
  395. // 倒计时结束后启用按钮
  396. isCodeBtnDisabled.value = false;
  397. }
  398. }, 1000);
  399. return;
  400. }
  401. function openAgreement() {
  402. // 打开用户协议
  403. console.log("打开用户协议");
  404. uni.navigateTo({
  405. url: "/pages/start/agreement/agreement",
  406. });
  407. }
  408. function openPrivacy() {
  409. // 打开隐私政策
  410. console.log("打开隐私政策");
  411. uni.navigateTo({
  412. url: "/pages/start/privacy/privacy",
  413. });
  414. }
  415. function changeCheckbox() {
  416. agreed.value = !agreed.value;
  417. checkboxUrl.value = agreed.value
  418. ? "../../../static/icons/Check-one-true.png"
  419. : "../../../static/icons/Check-one-false.png";
  420. }
  421. // 验证手机号是否正确
  422. function validatePhoneNumber(countryCode, phoneNumber) {
  423. // 检查是否为空
  424. if (!phoneNumber || phoneNumber.trim() === "") {
  425. uni.showToast({
  426. title: "手机号不能为空",
  427. icon: "none",
  428. });
  429. return false;
  430. }
  431. const bool = verificationPhone(countryCode, phoneNumber);
  432. console.log("验证是否成功", bool);
  433. // 检查格式是否正确
  434. if (!bool) {
  435. uni.showToast({
  436. title: "手机号格式不正确",
  437. icon: "none",
  438. });
  439. return false;
  440. }
  441. // 去掉+号后检查长度(手机号通常在7到15位之间)
  442. const cleanNumber = phoneNumber.replace(/^\+/, "");
  443. if (cleanNumber.length < 7 || cleanNumber.length > 15) {
  444. uni.showToast({
  445. title: "手机号长度不正确",
  446. icon: "none",
  447. });
  448. return false;
  449. }
  450. return true;
  451. }
  452. // 添加弹窗引用
  453. const agreementPopup = ref(null);
  454. // 处理同意按钮点击
  455. function handleAgree() {
  456. // 关闭弹窗
  457. agreementPopup.value.close();
  458. // 设置为已同意
  459. agreed.value = true;
  460. checkboxUrl.value = "../../../static/icons/Check-one-true.png";
  461. // 继续登录流程
  462. }
  463. </script>
  464. <style scoped>
  465. .login-registration-container {
  466. display: flex;
  467. flex-direction: column;
  468. align-items: center;
  469. justify-content: center;
  470. padding: 0 70rpx;
  471. height: 100vh;
  472. background-color: #ffffff;
  473. }
  474. /* 自定义导航栏样式 */
  475. .custom-navbar {
  476. position: absolute;
  477. top: 0;
  478. left: 0;
  479. /* z-index: 999; */
  480. width: 90%;
  481. height: 80rpx;
  482. display: flex;
  483. justify-content: space-between;
  484. align-items: center;
  485. padding: 10rpx 40rpx;
  486. margin-bottom: 20rpx;
  487. }
  488. .nav-left,
  489. .nav-right {
  490. flex: 1;
  491. }
  492. .nav-right {
  493. display: flex;
  494. justify-content: flex-end;
  495. }
  496. .icons {
  497. margin: 20rpx;
  498. width: 40rpx;
  499. height: 40rpx;
  500. /* margin-right: 10rpx; */
  501. }
  502. .back-btn,
  503. .headphone-btn {
  504. font-size: 36rpx;
  505. font-weight: bold;
  506. color: #333333;
  507. padding: 10rpx;
  508. }
  509. .logo {
  510. width: 120rpx;
  511. height: 120rpx;
  512. margin-bottom: 60rpx;
  513. border-radius: 20%;
  514. }
  515. .welcome-text {
  516. font-size: 48rpx;
  517. font-weight: bold;
  518. color: #333333;
  519. margin-bottom: 60rpx;
  520. /* text-align: left; */
  521. /* align-self: flex-start; */
  522. }
  523. .switch-container {
  524. display: flex;
  525. margin-bottom: 40rpx;
  526. align-self: flex-start;
  527. }
  528. .switch-item {
  529. font-size: 28rpx;
  530. color: #999999;
  531. padding: 10rpx 20rpx;
  532. position: relative;
  533. }
  534. .switch-item::after {
  535. content: "";
  536. position: absolute;
  537. bottom: 0;
  538. left: 50%;
  539. transform: translateX(-50%);
  540. width: 60%;
  541. /* 控制边框宽度 */
  542. height: 2rpx;
  543. background-color: transparent;
  544. }
  545. .switch-item.active {
  546. color: #333333;
  547. font-weight: 700;
  548. }
  549. .switch-item.active::after {
  550. content: "";
  551. position: absolute;
  552. top: 60rpx;
  553. bottom: 0;
  554. left: 50%;
  555. transform: translateX(-50%);
  556. width: 30%;
  557. /* 控制边框宽度 */
  558. height: 7rpx;
  559. background-color: #333333;
  560. }
  561. .input-container {
  562. width: 100%;
  563. }
  564. /* 添加图标输入框样式 */
  565. .input-with-icon {
  566. display: flex;
  567. align-items: center;
  568. width: 100%;
  569. height: 80rpx;
  570. border-bottom: 2rpx solid #e5e5e5;
  571. margin-bottom: 20rpx;
  572. }
  573. .input-icon {
  574. width: 40rpx;
  575. height: 40rpx;
  576. margin: 0 20rpx;
  577. }
  578. .input-field {
  579. flex: 1;
  580. height: 80rpx;
  581. padding: 15rpx 0;
  582. font-size: 28rpx;
  583. color: #333333;
  584. border: none;
  585. background-color: transparent;
  586. }
  587. .phone-input-container {
  588. display: flex;
  589. align-items: center;
  590. width: 95.8%;
  591. height: 80rpx;
  592. /* border-radius: 20rpx; */
  593. /* border: 2rpx solid #e5e5e5; */
  594. /* background-color: #f5f5f5; */
  595. padding: 0 10rpx;
  596. border-bottom: 2rpx solid #e5e5e5;
  597. margin-bottom: 20rpx;
  598. }
  599. .country-code-selector {
  600. display: flex;
  601. align-items: center;
  602. padding: 0 10rpx;
  603. padding-bottom: 1rpx;
  604. height: 100%;
  605. /* border-right: 2rpx solid #e5e5e5; */
  606. /* background-color: #f5f5f5; */
  607. border-radius: 20rpx 0 0 20rpx;
  608. }
  609. .country-code {
  610. font-size: 28rpx;
  611. color: #333333;
  612. margin-right: 10rpx;
  613. }
  614. .country-flag-img {
  615. width: 40rpx;
  616. height: 40rpx;
  617. margin-right: 10rpx;
  618. }
  619. .arrow-down {
  620. font-size: 20rpx;
  621. color: #999999;
  622. }
  623. .phone-input {
  624. flex: 1;
  625. width: auto;
  626. height: 100%;
  627. border: none;
  628. background-color: transparent;
  629. padding: 0 0rpx;
  630. }
  631. .send-code-btn {
  632. width: 200rpx;
  633. height: 60rpx;
  634. display: inline-flex;
  635. padding: 0rpx 10rpx;
  636. justify-content: center;
  637. align-items: center;
  638. gap: 10px;
  639. border-radius: 4px;
  640. background: #000;
  641. }
  642. .send-code-btn-email {
  643. width: 200rpx;
  644. height: 60rpx;
  645. display: inline-flex;
  646. padding: 0rpx 10rpx;
  647. justify-content: center;
  648. align-items: center;
  649. gap: 10px;
  650. border-radius: 4px;
  651. background: #000;
  652. margin-right: 15rpx;
  653. }
  654. .send-code-btn-disabled {
  655. background: #e6e6e6;
  656. /* 禁用状态下的灰色背景 */
  657. }
  658. .send-code-btn-disabled-text {
  659. color: #999999 !important;
  660. }
  661. .send-code-text {
  662. color: #fff;
  663. font-size: 28rpx;
  664. }
  665. .agreement-container-one {
  666. display: flex;
  667. align-items: center;
  668. align-self: flex-start;
  669. margin-bottom: 80rpx;
  670. }
  671. .agreement-container {
  672. display: flex;
  673. align-items: center;
  674. margin-bottom: 40rpx;
  675. margin-top: -75.5rpx;
  676. align-self: flex-start;
  677. }
  678. .checkbox {
  679. width: 30rpx;
  680. height: 30rpx;
  681. margin-left: 20rpx;
  682. /* flex: content; */
  683. }
  684. .agreement-text-one {
  685. font-size: 22rpx;
  686. color: #666666;
  687. text-align: center;
  688. margin-left: 10rpx;
  689. }
  690. .agreement-text {
  691. margin-left: 20rpx;
  692. font-size: 24rpx;
  693. color: #666666;
  694. }
  695. .link {
  696. color: #333333;
  697. font-weight: bold;
  698. text-decoration: underline;
  699. }
  700. .register-btn {
  701. width: 100%;
  702. height: 80rpx;
  703. background-color: #000000;
  704. color: white;
  705. font-size: 32rpx;
  706. font-weight: bold;
  707. border-radius: 40rpx;
  708. margin-bottom: 40rpx;
  709. }
  710. .or-text {
  711. flex-direction: column;
  712. font-size: 24rpx;
  713. color: #999999;
  714. margin-top: 330rpx;
  715. margin-bottom: -22rpx;
  716. }
  717. .third-party-login {
  718. width: 100%;
  719. margin-bottom: 60rpx;
  720. }
  721. .third-party-text {
  722. color: #ffffff;
  723. font-weight: bold;
  724. white-space: pre;
  725. }
  726. .third-party-btn {
  727. width: 100%;
  728. height: 80rpx;
  729. background-color: rgb(0, 0, 0);
  730. border: 2rpx solid #e5e5e5;
  731. border-radius: 40rpx;
  732. display: flex;
  733. align-items: center;
  734. justify-content: center;
  735. margin-bottom: 20rpx;
  736. font-size: 28rpx;
  737. color: #333333;
  738. }
  739. .google-icon,
  740. .apple-icon {
  741. width: 60rpx;
  742. height: 60rpx;
  743. margin-right: 20rpx;
  744. }
  745. .existing-account {
  746. display: flex;
  747. align-items: center;
  748. }
  749. .account-text {
  750. font-size: 24rpx;
  751. color: #666666;
  752. }
  753. .login-link {
  754. font-size: 24rpx;
  755. font-weight: bold;
  756. color: #333333;
  757. margin-left: 10rpx;
  758. text-decoration: underline;
  759. }
  760. .static-footer {
  761. position: fixed;
  762. bottom: 0;
  763. }
  764. /* 弹窗样式 */
  765. .popup-content {
  766. background-color: #ffffff;
  767. padding: 40rpx;
  768. text-align: center;
  769. border-radius: 10rpx;
  770. width: 550rpx;
  771. }
  772. .popup-title {
  773. font-size: 36rpx;
  774. font-weight: bold;
  775. color: #333;
  776. margin-bottom: 80rpx;
  777. text-align: center; /* 水平居中 */
  778. display: flex; /* 使用flex布局 */
  779. justify-content: center; /* 水平居中 */
  780. align-items: center; /* 垂直居中 */
  781. }
  782. .popup-message {
  783. font-size: 28rpx;
  784. color: #000000;
  785. margin-bottom: 80rpx;
  786. text-align: center; /* 水平居中 */
  787. display: flex; /* 使用flex布局 */
  788. justify-content: center; /* 水平居中 */
  789. align-items: center; /* 垂直居中 */
  790. }
  791. .agree-button {
  792. width: 300rpx;
  793. height: 80rpx;
  794. background-color: #000000;
  795. border-radius: 40rpx;
  796. display: flex; /* 添加flex布局 */
  797. align-items: center; /* 垂直居中 */
  798. justify-content: center; /* 水平居中 */
  799. }
  800. .agree-text {
  801. color: #ffffff;
  802. font-size: 34rpx;
  803. /* 添加垂直居中相关样式 */
  804. display: flex;
  805. align-items: center;
  806. justify-content: center;
  807. line-height: 1; /* 确保文字垂直居中 */
  808. }
  809. </style>