index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <template>
  2. <div class="login-wrapper bglogin">
  3. <up-navbar title="登录" placeholder>
  4. <template #left>
  5. <view class=""></view>
  6. </template>
  7. </up-navbar>
  8. <div class="shading">
  9. <view class="">Hello!</view>
  10. <view class="">欢迎来到水贝搬运工</view>
  11. </div>
  12. <div class="whiteBg" v-if="formItem === 1">
  13. <view class="whiteBg-tab whiteBg-tabimg" :class="{'whiteBg-tabs whiteBg-tabsimg':current == 1}">
  14. <view @click="current = item.type" :class="{active:current != index,noActive:current == index}" class="whiteBg-tab-li" v-for="(item,index) in navList" :key="index">
  15. <view class="">{{ item.name }}</view>
  16. </view>
  17. </view>
  18. <div class="list">
  19. <div class="item">
  20. <input type="text" maxlength="11" class="texts" placeholder="输入手机号码" v-model="account" required />
  21. </div>
  22. <template v-if="current == 0">
  23. <div class="item">
  24. <input type="password" class="texts" placeholder="填写登录密码" v-model="password" required />
  25. </div>
  26. </template>
  27. <template v-else-if="current == 1">
  28. <div class="item">
  29. <input type="number" placeholder="填写验证码" class="texts" v-model="captcha" maxlength="6" />
  30. <button class="code" :disabled="disabled" :class="disabled === true ? 'on' : ''" @click="code">
  31. {{ text }}
  32. </button>
  33. </div>
  34. <div class="item">
  35. <input type="text" class="texts" placeholder="输入邀请码(非必填)" v-model="inviteCode" />
  36. </div>
  37. </template>
  38. </div>
  39. <div class="privacy-box" @click="aloneChecked = !aloneChecked">
  40. <image v-if="aloneChecked" src="/static/images/select-active@2x.png" mode=""></image>
  41. <image v-else src="/static/images/select@2x.png" mode=""></image>
  42. <view class="">我已阅读并同意</view>
  43. <view class="privacy-text" @click.stop="openPrivacy">《用户协议》</view>
  44. <view class="privacy-text" @click.stop="openPrivacy">《隐私政策》</view>
  45. </div>
  46. <view class="footer">
  47. <div class="logon" @click="loginMobile" v-if="current !== 0">登录</div>
  48. <div class="logon" @click="submit" v-if="current === 0">登录</div>
  49. </view>
  50. </div>
  51. </div>
  52. </template>
  53. <script setup>
  54. import {
  55. ref,
  56. watch,
  57. onMounted
  58. } from "vue";
  59. import {
  60. onLoad
  61. } from "@dcloudio/uni-app";
  62. import {
  63. useAppStore
  64. } from "@/stores/app";
  65. import {
  66. loginH5,
  67. loginMobile as loginMobileApi,
  68. registerVerify,
  69. register,
  70. getUserInfo,
  71. } from "@/api/user";
  72. import {
  73. getLogo,
  74. appAuth,
  75. appleLogin
  76. } from "@/api/public";
  77. import {
  78. VUE_APP_API_URL
  79. } from "@/utils";
  80. import {
  81. useSendCode
  82. } from "@/hooks/useSendCode";
  83. import Cache from "@/utils/cache";
  84. import {
  85. useToast
  86. } from "@/hooks/useToast.js";
  87. // import {
  88. // useGoEasy
  89. // } from "@/plugin/goeasy";
  90. // import {
  91. // getGroupchatList
  92. // } from "@/api/customerService";
  93. import { footprintScan } from "@/api/merchant.js";
  94. import {
  95. EXPIRES_TIME
  96. } from "@/config/cache";
  97. const appStore = useAppStore();
  98. const {
  99. Toast
  100. } = useToast();
  101. // const {
  102. // connect,
  103. // GoEasy
  104. // } = useGoEasy();
  105. const BACK_URL = "login_back_url";
  106. // Reactive state
  107. const navList = ref([{name:"快速登录",type:1}, {name:"账号登录",type:0}]);
  108. const current = ref(1);
  109. const account = ref("");
  110. const inviteCode = ref(""); // 邀请码
  111. const password = ref("");
  112. const captcha = ref("");
  113. const formItem = ref(1);
  114. const type = ref("login");
  115. const logoUrl = ref("");
  116. const keyCode = ref("");
  117. const codeUrl = ref("");
  118. const codeVal = ref("");
  119. const platform = ref("");
  120. const appleShow = ref(false);
  121. const aloneChecked = ref(false);
  122. const merchantId = ref('');
  123. const userInfo = ref({});
  124. // Watch formItem to update type
  125. watch(formItem, (newVal) => {
  126. type.value = newVal === 1 ? "login" : "register";
  127. });
  128. const {
  129. disabled,
  130. text,
  131. sendCode
  132. } = useSendCode();
  133. function openPrivacy() {
  134. void plus.runtime.openWeb("https://www.shuibeibyg.com/shenhe.html");
  135. }
  136. // Get logo image
  137. const getLogoImage = async () => {
  138. try {
  139. const res = await getLogo();
  140. logoUrl.value = res.data.logoUrl || "/static/images/logo2.png";
  141. } catch (err) {
  142. console.error(err);
  143. }
  144. };
  145. // 验证码登录
  146. const loginMobile = async () => {
  147. if (!account.value)
  148. return uni.showToast({
  149. title: "请填写手机号码",
  150. icon: "none"
  151. });
  152. if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
  153. return uni.showToast({
  154. title: "请输入正确的手机号码",
  155. icon: "none"
  156. });
  157. if (!captcha.value)
  158. return uni.showToast({
  159. title: "请填写验证码",
  160. icon: "none"
  161. });
  162. if (!/^[\w\d]+$/i.test(captcha.value))
  163. return uni.showToast({
  164. title: "请输入正确的验证码",
  165. icon: "none"
  166. });
  167. if (!aloneChecked.value) {
  168. return Toast({
  169. title: "请阅读并同意用户协议和隐私政策",
  170. icon: "none"
  171. });
  172. }
  173. try {
  174. const res = await loginMobileApi({
  175. phone: account.value,
  176. captcha: captcha.value,
  177. platformType: "app",
  178. spread_spid: Cache.get("spread"),
  179. inviteCode: inviteCode.value,
  180. });
  181. // 使用 LOGIN action 保存 token 到缓存
  182. appStore.LOGIN({
  183. token: res.data.token
  184. });
  185. // 保存过期时间(999天后过期)
  186. const expiresTime = Math.round(new Date() / 1000) + 999 * 24 * 60 * 60;
  187. Cache.set(EXPIRES_TIME, expiresTime, 0);
  188. await getUserInfoFn(res.data);
  189. } catch (res) {
  190. const message = typeof res === "object" ? res.message : res;
  191. uni.showToast({
  192. title: message,
  193. icon: "none"
  194. });
  195. }
  196. };
  197. // 注册
  198. const registerFn = async () => {
  199. if (!account.value)
  200. return uni.showToast({
  201. title: "请填写手机号码",
  202. icon: "none"
  203. });
  204. if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
  205. return uni.showToast({
  206. title: "请输入正确的手机号码",
  207. icon: "none"
  208. });
  209. if (!captcha.value)
  210. return uni.showToast({
  211. title: "请填写验证码",
  212. icon: "none"
  213. });
  214. if (!/^[\w\d]+$/i.test(captcha.value))
  215. return uni.showToast({
  216. title: "请输入正确的验证码",
  217. icon: "none"
  218. });
  219. if (!password.value)
  220. return uni.showToast({
  221. title: "请填写密码",
  222. icon: "none"
  223. });
  224. // 正则1:验证长度6-18位
  225. const lengthReg = /^.{6,18}$/;
  226. // 正则2:必须包含至少1个数字(\d)和至少1个字母(a-zA-Z)
  227. const hasNumAndLetterReg = /(?=.*\d)(?=.*[a-zA-Z])/;
  228. if (!lengthReg.test(password.value)) {
  229. return uni.showToast({
  230. title: "密码长度必须为6-18位",
  231. icon: "none"
  232. });
  233. } else if (!hasNumAndLetterReg.test(password.value)) {
  234. return uni.showToast({
  235. title: "密码必须同时包含数字和字母",
  236. icon: "none"
  237. });
  238. }
  239. try {
  240. const res = await register({
  241. account: account.value,
  242. captcha: captcha.value,
  243. password: password.value,
  244. spread: Cache.get("spread"),
  245. });
  246. uni.showToast({
  247. title: res.message,
  248. icon: "none"
  249. });
  250. formItem.value = 1;
  251. } catch (res) {
  252. uni.showToast({
  253. title: res.message,
  254. icon: "none"
  255. });
  256. }
  257. };
  258. // 发送验证码
  259. const code = async () => {
  260. if (!account.value)
  261. return uni.showToast({
  262. title: "请填写手机号码",
  263. icon: "none"
  264. });
  265. if (!/^1(3|4|5|7|8|9|6)\d{9}$/i.test(account.value))
  266. return uni.showToast({
  267. title: "请输入正确的手机号码",
  268. icon: "none"
  269. });
  270. if (formItem.value === 2) type.value = "register";
  271. try {
  272. const res = await registerVerify(account.value);
  273. uni.showToast({
  274. title: res.message,
  275. icon: "none"
  276. });
  277. sendCode();
  278. } catch (err) {
  279. uni.showToast({
  280. title: err.message,
  281. icon: "none"
  282. });
  283. }
  284. };
  285. // Navigation tab switch
  286. const navTap = (index) => {
  287. current.value = index;
  288. };
  289. // 账号密码登录
  290. const submit = async () => {
  291. if (!account.value) return Toast({
  292. title: "请填写账号",
  293. icon: "none"
  294. });
  295. if (!/^[\w\d]{5,16}$/i.test(account.value))
  296. return Toast({
  297. title: "请输入正确的账号",
  298. icon: "none"
  299. });
  300. if (!password.value) return Toast({
  301. title: "请填写密码",
  302. icon: "none"
  303. });
  304. if (!aloneChecked.value) {
  305. return Toast({
  306. title: "请阅读并同意用户协议和隐私政策",
  307. icon: "none"
  308. });
  309. }
  310. try {
  311. const {
  312. data
  313. } = await loginH5({
  314. account: account.value,
  315. password: password.value,
  316. platformType: "app",
  317. spread: Cache.get("spread"),
  318. });
  319. appStore.LOGIN({
  320. token: data.token
  321. });
  322. // 保存过期时间(999天后过期)
  323. const expiresTime = Math.round(new Date() / 1000) + 999 * 24 * 60 * 60;
  324. Cache.set(EXPIRES_TIME, expiresTime, 0);
  325. await getUserInfoFn(data);
  326. } catch (err) {
  327. const message = typeof err === "object" ? err.message : err;
  328. uni.showToast({
  329. title: message,
  330. icon: "none",
  331. });
  332. }
  333. };
  334. // 订阅已加入的群组
  335. // function getGroupchatListFn(uid) {
  336. // getGroupchatList({
  337. // userId: uid
  338. // }).then((res) => {
  339. // if (res && res.data && res.data.length > 0) {
  340. // //订阅群消息
  341. // var groupIds = res.data;
  342. // GoEasy.im.subscribeGroup({
  343. // groupIds: groupIds,
  344. // onSuccess: function() {
  345. // //订阅成功
  346. // console.log("Group message subscribe successfully.");
  347. // },
  348. // onFailed: function(error) {
  349. // //订阅失败
  350. // console.log(
  351. // "Failed to subscribe group message, code:" +
  352. // error.code +
  353. // " content:" +
  354. // error.content
  355. // );
  356. // },
  357. // });
  358. // }
  359. // });
  360. // }
  361. const getUserInfoFn = async (data) => {
  362. try {
  363. appStore.SETUID(data.uid);
  364. const res = await getUserInfo();
  365. appStore.UPDATE_USERINFO(res.data);
  366. userInfo.value = res.data;
  367. // connect({
  368. // id: appStore.uid?.toString(),
  369. // data: {
  370. // avatar: appStore.$userInfo?.avatar,
  371. // nickname: appStore.$userInfo?.nickname,
  372. // phone: appStore.$userInfo?.phone,
  373. // uid: appStore.uid?.toString(),
  374. // },
  375. // });
  376. // 订阅已加入的群组
  377. // await getGroupchatListFn(data.uid);
  378. if(merchantId.value != ''){
  379. let obj ={
  380. merchantId:merchantId.value,
  381. userId:res.data.userId
  382. }
  383. await footprintScanFn(obj)
  384. }else{
  385. console.log('============')
  386. appStore.UPDATE_MERCHANT_ID('')
  387. Toast({ title: "登录成功" });
  388. backHome();
  389. }
  390. } catch (err) {
  391. console.error(err);
  392. uni.showToast({
  393. title: err.msg,
  394. icon: "none"
  395. });
  396. }
  397. };
  398. const backHome = () => {
  399. if(userInfo.value.merchant && userInfo.value.merchant.id){
  400. uni.navigateTo({
  401. url:"/pages/merchantCenter/index"
  402. })
  403. }else{
  404. uni.switchTab({
  405. url: "/pages/index/index",
  406. });
  407. }
  408. };
  409. const footprintScanFn =async (data) => {
  410. const res = await footprintScan(data);
  411. Toast({ title: "登录成功" });
  412. backHome();
  413. }
  414. onLoad(() => {
  415. merchantId.value = appStore.merchantId || '';
  416. getLogoImage();
  417. });
  418. onMounted(() => {
  419. uni.getSystemInfo({
  420. success(res) {
  421. platform.value = res.platform.toLowerCase();
  422. if (platform.value === "ios" && res.system.split(" ")[1] >= 13) {
  423. appleShow.value = true;
  424. }
  425. },
  426. });
  427. });
  428. </script>
  429. <style lang="scss" scoped>
  430. ::v-deep .u-status-bar {
  431. background: transparent;
  432. background-color: transparent !important;
  433. }
  434. ::v-deep .u-navbar__content {
  435. background: transparent;
  436. background-color: transparent !important;
  437. }
  438. .login-wrapper {
  439. //background: url('https://sb-admin.oss-cn-shenzhen.aliyuncs.com/shuibei-mini/new-mini/login@2x.png');
  440. background-repeat: no-repeat;
  441. background-size: 100% 624rpx;
  442. background-color: #FFFFFF;
  443. }
  444. .appLogin {
  445. .hds {
  446. display: flex;
  447. justify-content: center;
  448. align-items: center;
  449. font-size: 24rpx;
  450. color: #b4b4b4;
  451. .line {
  452. width: 68rpx;
  453. height: 1rpx;
  454. background: #cccccc;
  455. }
  456. p {
  457. margin: 0 20rpx;
  458. }
  459. }
  460. .btn-wrapper {
  461. display: flex;
  462. align-items: center;
  463. justify-content: center;
  464. margin-top: 30rpx;
  465. .btn {
  466. display: flex;
  467. align-items: center;
  468. justify-content: center;
  469. width: 68rpx;
  470. height: 68rpx;
  471. border-radius: 50%;
  472. }
  473. .apple-btn {
  474. display: flex;
  475. align-items: center;
  476. justify-content: center;
  477. margin-left: 30rpx;
  478. background: #000;
  479. border-radius: 34rpx;
  480. font-size: 40rpx;
  481. .icon-s-pingguo {
  482. color: #fff;
  483. font-size: 40rpx;
  484. }
  485. }
  486. .iconfont {
  487. font-size: 40rpx;
  488. color: #fff;
  489. }
  490. .wx {
  491. margin-right: 30rpx;
  492. background-color: #61c64f;
  493. }
  494. .mima {
  495. background-color: #28b3e9;
  496. }
  497. .yanzheng {
  498. background-color: #f89c23;
  499. }
  500. }
  501. }
  502. .code img {
  503. width: 100%;
  504. height: 100%;
  505. }
  506. .acea-row.row-middle {
  507. input {
  508. margin-left: 20rpx;
  509. display: block;
  510. }
  511. }
  512. .login-wrapper {
  513. .shading {
  514. padding: 72rpx 78rpx;
  515. display: flex;
  516. justify-content: center;
  517. flex-direction: column;
  518. view {
  519. color: #333333;
  520. font-size: 36rpx;
  521. font-weight: bold;
  522. &:first-child {
  523. font-size: 48rpx;
  524. margin-bottom: 16rpx;
  525. }
  526. }
  527. }
  528. .whiteBg {
  529. margin-top: 100rpx;
  530. background: #FFFFFF;
  531. border-radius: 48rpx 48rpx 0rpx 0rpx;
  532. .whiteBg-tab {
  533. color: #666666;
  534. display: flex;
  535. align-items: center;
  536. height: 88rpx;
  537. text-align: center;
  538. //background: url('https://sb-admin.oss-cn-shenzhen.aliyuncs.com/shuibei-mini/new-mini/logintab1.png');
  539. background-repeat: no-repeat;
  540. background-size: cover;
  541. .active {
  542. width: 407rpx;
  543. view {
  544. color: #F8C008;
  545. font-weight: bold;
  546. position: relative;
  547. &::after {
  548. left: 50%;
  549. bottom: -12rpx;
  550. content: '';
  551. transform: translateX(-50%);
  552. position: absolute;
  553. width: 32rpx;
  554. height: 8rpx;
  555. background: #F8C008;
  556. border-radius: 4rpx;
  557. }
  558. }
  559. }
  560. .noActive {
  561. flex: 1;
  562. }
  563. }
  564. .whiteBg-tabs {
  565. //background: url('https://sb-admin.oss-cn-shenzhen.aliyuncs.com/shuibei-mini/new-mini/logintab.png');
  566. background-repeat: no-repeat;
  567. background-size: cover;
  568. }
  569. .list {
  570. padding: 0 80rpx;
  571. margin-top: 120rpx;
  572. .item {
  573. display: flex;
  574. height: 88rpx;
  575. align-items: center;
  576. justify-content: space-between;
  577. padding: 0 8rpx 0 24rpx;
  578. background: #F9F7F0;
  579. border-radius: 16rpx;
  580. margin-bottom: 32rpx;
  581. .texts {
  582. flex: 1;
  583. font-size: 28rpx;
  584. }
  585. .code {
  586. padding: 0;
  587. margin: 0;
  588. width: 188rpx;
  589. display: flex;
  590. align-items: center;
  591. height: 72rpx;
  592. justify-content: center;
  593. background: #FFFFFF;
  594. border-radius: 16rpx;
  595. color: #F8C008;
  596. font-size: 28rpx;
  597. font-weight: bold;
  598. border: none;
  599. &::after {
  600. width: 0;
  601. border: none;
  602. }
  603. }
  604. }
  605. }
  606. .logon {
  607. display: flex;
  608. align-items: center;
  609. justify-content: center;
  610. height: 88rpx;
  611. background-color: #F8C008;
  612. border-radius: 16rpx;
  613. color: #333333;
  614. font-size: 32rpx;
  615. font-weight: bold;
  616. }
  617. }
  618. .privacy-box {
  619. font-size: 24rpx;
  620. padding: 0 80rpx;
  621. display: flex;
  622. align-items: center;
  623. image {
  624. width: 28rpx;
  625. height: 28rpx;
  626. margin-right: 8rpx;
  627. }
  628. .privacy-text {
  629. color: #F8C008;
  630. vertical-align: top;
  631. }
  632. }
  633. }
  634. .footer {
  635. padding: 48rpx 80rpx;
  636. }
  637. </style>