Taro Mini Program Development Крупномасштабная практика (3): Реализация WeChat и Alipay Multi-terminal Login

Апплет WeChat
Taro Mini Program Development Крупномасштабная практика (3): Реализация WeChat и Alipay Multi-terminal Login

Добро пожаловать, чтобы продолжить чтение серии «Крупномасштабное сражение по разработке мини-программы Таро», обзор предыдущей ситуации:

В этой статье мы реализуем многотерминальный вход WeChat и Alipay. Если вы хотите начать прямо с этого, выполните следующую команду:

git clone -b third-part https://github.com/tuture-dev/ultra-club.git
cd ultra-club

Исходный код, задействованный в этой статье, размещен вGithubНа, если вы считаете, что мы написали неплохо, я надеюсь, что вы можете поставить лайк этой статье ❤️ + звезда репозитория Github ❤️ о~

Перед официальным стартом мы надеемся, что вы обладаете следующими знаниями:

  • Базовые знания фреймворка React см.эта статьяучиться
  • Для часто используемых хуков React (useState,useEffect), а сообщество Tuque запустит «Время для чашки чая, начните работу с React Hooks», так что следите за обновлениями!

В дополнение к этому вам также потребуется скачать и установитьИнструменты разработчика Alipayи создайте свой собственный идентификатор мини-программы после входа в систему.

Мультитерминальный логин, группа демонов дико танцует

По сравнению с обычными веб-приложениями, апплет может реализовать вход в один клик на той платформе, где он находится, что очень удобно. На этом этапе мы также внедрим многотерминальный вход в систему (в основном, включая вход в WeChat и вход в Alipay). Причина, по которой название называется «Танцы демонов», не только вдохновлена ​​редакторами «Shocked», но и тем, что способы, которыми различные платформы обрабатывают вход и аутентификацию, сегодня сильно различаются. наступить на многое.«Яма» действительно может реализовать «мультитерминальный вход».

Готов к работе

Планирование проектирования компонентов

Код в этом разделе очень длинный, поэтому перед официальным стартом давайте проверим планирование дизайна компонента, чтобы вы могли иметь четкое представление о работе, которую мы будем делать дальше.

Вы можете видеть, что страница «Моя» разбита в целом наHeaderа такжеFooter:

  • HeaderвключатьLoggedMine(личная информация), и если вы не вошли в системуLoginButton(обычная кнопка входа),WeappLoginButton(Кнопка входа в WeChat, которая появляется только в апплете WeChat) иAlipayLoginButton(кнопка входа в Alipay, которая появляется только в апплете Alipay)
  • FooterТекст, используемый для отображения того, вошли ли вы в систему, он будет отображаться, если вы вошли в систему.Logout(кнопка выхода)

Настройте плагин Babel

С этого шага мы впервые начнем писать асинхронный код. Этот проект будет использовать популярный async/await для написания асинхронной логики, поэтому давайте настроим соответствующий плагин Babel:

npm install babel-plugin-transform-runtime --save-dev
# yarn add babel-plugin-transform-runtime -D

затем вconfig/index.jsЧжунвэйconfig.babel.pluginsДобавьте соответствующую конфигурацию следующим образом:

const config = {
  // ...
  babel: {
    // ...
    plugins: [
      // ...
      [
        'transform-runtime',
        {
          helpers: false,
          polyfill: false,
          regenerator: true,
          moduleName: 'babel-runtime',
        },
      ],
    ],
  },
  // ...
}

// ...

Реализация каждого компонента

Реализовать кнопку входа

Во-первых, давайте реализуем обычную кнопку входа в системуLoginButtonкомпоненты. Создайтеsrc/components/LoginButtonкаталог, в котором создатьindex.js, код показан ниже:

import Taro from '@tarojs/taro'
import { AtButton } from 'taro-ui'

export default function LoginButton(props) {
  return (
    <AtButton type="primary" onClick={props.handleClick}>
      普通登录
    </AtButton>
  )
}

Мы использовали Taro UIAtButtonкомпонент и определяетhandleClickСобытия, которые будут переданы позже при использовании.

Реализовать WeappLoginButton

Затем мы реализуем кнопку входа в WeChat.WeappLoginButton. Создайтеsrc/components/WeappLoginButtonкаталог, в котором создатьindex.jsа такжеindex.scss.index.jsкод показывает, как показано ниже:

import Taro, { useState } from '@tarojs/taro'
import { Button } from '@tarojs/components'

import './index.scss'

export default function LoginButton(props) {
  const [isLogin, setIsLogin] = useState(false)

  async function onGetUserInfo(e) {
    setIsLogin(true)

    const { avatarUrl, nickName } = e.detail.userInfo
    await props.setLoginInfo(avatarUrl, nickName)

    setIsLogin(false)
  }

  return (
    <Button
      openType="getUserInfo"
      onGetUserInfo={onGetUserInfo}
      type="primary"
      className="login-button"
      loading={isLogin}
    >
      微信登录
    </Button>
  )
}

Видно, что в кнопке входа в WeChat и предыдущей обычной кнопке входа гораздо больше вещей:

  • ДобавленisLoginСтатус, который используется для указания, ожидает ли он входа в систему и следует ли изменить статус.setIsLoginфункция
  • ДостигнутоonGetUserInfoАсинхронная функция используется для обработки логики после того, как пользователь нажимает кнопку входа и получает информацию. Среди них мы передаем полученную информацию о пользователеpropsсерединаsetLoginInfo, тем самым изменяя статус входа всего приложения
  • ДобавленopenType(возможность открытия WeChat), здесь мы вводимgetUserInfo(чтобы получить информацию о пользователе), чтобы увидеть все поддерживаемые открытые типы, см.Wechat Open Document Соответствующий раздел
  • ДобавленonGetUserInfoЭтот обработчик используется для записи логики обработки после получения информации о пользователе, вот только что реализованный входящийonGetUserInfo

WeappLoginButtonстильindex.scssкод показывает, как показано ниже:

.login-button {
  width: 100%;
  margin-top: 40px;
  margin-bottom: 40px;
}

Реализовать AlipayLoginButton

Давайте реализуем компонент кнопки входа в Alipay. Создайтеsrc/components/AlipayLoginButtonкаталог, в котором создатьindex.jsа такжеindex.scss.index.jsкод показывает, как показано ниже:

import Taro, { useState } from '@tarojs/taro'
import { Button } from '@tarojs/components'

import './index.scss'

export default function LoginButton(props) {
  const [isLogin, setIsLogin] = useState(false)

  async function onGetAuthorize(res) {
    setIsLogin(true)
    try {
      let userInfo = await Taro.getOpenUserInfo()

      userInfo = JSON.parse(userInfo.response).response
      const { avatar, nickName } = userInfo

      await props.setLoginInfo(avatar, nickName)
    } catch (err) {
      console.log('onGetAuthorize ERR: ', err)
    }

    setIsLogin(false)
  }

  return (
    <Button
      openType="getAuthorize"
      scope="userInfo"
      onGetAuthorize={onGetAuthorize}
      type="primary"
      className="login-button"
      loading={isLogin}
    >
      支付宝登录
    </Button>
  )
}

Как видите, содержимое в основном похоже на предыдущую кнопку входа в WeChat, но со следующими отличиями:

  • выполнитьonGetAuthorizeПерезвоните. В отличие от предыдущей функции обратного вызова WeChat, здесь мы должны вызватьTaro.getOpenUserInfoВручную получить основную информацию о пользователе (фактически позвоните на открытую платформу Alipaymy.getOpenUserInfo)
  • ButtonкомпонентopenType(возможность открытия Alipay) установлено наgetAuthorize(Авторизация мини-программы)
  • При установлении открытой емкости какgetAuthorize, вам нужно добавитьscopeсобственностьuserInfo, чтобы пользователь мог авторизовать апплет для получения основной информации о членах Alipay (другое допустимое значение —phoneNumber, используется для получения номера телефона)
  • входящийonGetAuthorizeПерезвоните

намекать

Подробную информацию о кнопке входа в апплет Alipay см.официальная документация.

файл стиляindex.scssКод выглядит следующим образом:

.login-button {
  width: 100%;
  margin-top: 40px;
}

Внедрить LoggedMine

Затем мы реализуем состояние входа в системуLoggedMineкомпоненты. Создайтеsrc/components/LoggedMineкаталог, в котором создатьindex.jsxа такжеindex.scss.index.jsxкод показывает, как показано ниже:

import Taro from '@tarojs/taro'
import { View, Image } from '@tarojs/components'
import PropTypes from 'prop-types'

import './index.scss'
import avatar from '../../images/avatar.png'

export default function LoggedMine(props) {
  const { userInfo = {} } = props
  function onImageClick() {
    Taro.previewImage({
      urls: [userInfo.avatar],
    })
  }

  return (
    <View className="logged-mine">
      <Image
        src={userInfo.avatar ? userInfo.avatar : avatar}
        className="mine-avatar"
        onClick={onImageClick}
      />
      <View className="mine-nickName">
        {userInfo.nickName ? userInfo.nickName : '图雀酱'}
      </View>
      <View className="mine-username">{userInfo.username}</View>
    </View>
  )
}

LoggedMine.propTypes = {
  avatar: PropTypes.string,
  nickName: PropTypes.string,
  username: PropTypes.string,
}

Здесь мы добавили функцию нажатия на аватарку для предварительного просмотра, доступ к которой можно получить черезTaro.previewImageфункциявыполнить.

LoggedMineФайл стиля для компонента выглядит следующим образом:

.logged-mine {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.mine-avatar {
  width: 200px;
  height: 200px;
  border-radius: 50%;
}

.mine-nickName {
  font-size: 40;
  margin-top: 20px;
}

.mine-username {
  font-size: 32px;
  margin-top: 16px;
  color: #777;
}

Реализовать компонент заголовка

После того, как все «мелкие детали» будут реализованы, мы реализуем весь интерфейс входа в систему.Headerчасть. Создайтеsrc/components/Headerкаталог, в котором создатьindex.jsа такжеindex.scss.index.jsкод показывает, как показано ниже:

import Taro from '@tarojs/taro'
import { View } from '@tarojs/components'
import { AtMessage } from 'taro-ui'

import LoggedMine from '../LoggedMine'
import LoginButton from '../LoginButton'
import WeappLoginButton from '../WeappLoginButton'
import AlipayLoginButton from '../AlipayLoginButton'

import './index.scss'

export default function Header(props) {
  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY

  return (
    <View className="user-box">
      <AtMessage />
      <LoggedMine userInfo={props.userInfo} />
      {!props.isLogged && (
        <View className="login-button-box">
          <LoginButton handleClick={props.handleClick} />
          {isWeapp && <WeappLoginButton setLoginInfo={props.setLoginInfo} />}
          {isAlipay && <AlipayLoginButton setLoginInfo={props.setLoginInfo} />}
        </View>
      )}
    </View>
  )
}

Видно, что мы основывались наTaro.ENV_TYPEПроверьте текущую платформу (WeChat, Alipay или другие), а затем определите, отображается ли кнопка входа соответствующей платформы.

намекать

Вы, возможно, нашли,setLoginInfoВсе еще нужно дождаться входящего родительского компонента. Хотя хуки упрощают определение и обновление состояния, они не упрощают логику изменения состояния компонентов. На следующем шаге мы упростим с помощью Redux.

HeaderКод стиля для компонента выглядит следующим образом:

.user-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

.login-button-box {
  margin-top: 60px;
  width: 100%;
}

Реализовать форму входа

Затем реализуем обычный логинLoginFormкомпоненты. Поскольку целью этой серии руководств является объяснение Таро, процесс регистрации/входа здесь упрощен.Пользователи могут напрямую ввести свое имя пользователя и загрузить аватар для регистрации/входа без установки паролей и других процессов проверки. Создайтеsrc/components/LoginFormкаталог, в котором создатьindex.jsxа такжеindex.scss.index.jsxкод показывает, как показано ниже:

import Taro, { useState } from '@tarojs/taro'
import { View, Form } from '@tarojs/components'
import { AtButton, AtImagePicker } from 'taro-ui'

import './index.scss'

export default function LoginForm(props) {
  const [showAddBtn, setShowAddBtn] = useState(true)

  function onChange(files) {
    if (files.length > 0) {
      setShowAddBtn(false)
    }

    props.handleFilesSelect(files)
  }

  function onImageClick() {
    Taro.previewImage({
      urls: [props.files[0].url],
    })
  }

  return (
    <View className="post-form">
      <Form onSubmit={props.handleSubmit}>
        <View className="login-box">
          <View className="avatar-selector">
            <AtImagePicker
              length={1}
              mode="scaleToFill"
              count={1}
              files={props.files}
              showAddBtn={showAddBtn}
              onImageClick={onImageClick}
              onChange={onChange}
            />
          </View>
          <Input
            className="input-nickName"
            type="text"
            placeholder="点击输入昵称"
            value={props.formNickName}
            onInput={props.handleNickNameInput}
          />
          <AtButton formType="submit" type="primary">
            登录
          </AtButton>
        </View>
      </Form>
    </View>
  )
}

Здесь мы используем Taro UIКомпонент выбора изображений ImagePicker, что позволяет пользователю выбрать изображение для загрузки.AtImagePickerНаиболее важным атрибутом являетсяonChangeФункция обратного вызова, здесь мы передаем ее через родительский компонентhandleFilesSelectфункция, чтобы сделать это.

LoginFormКод стиля для компонента выглядит следующим образом:

.post-form {
  margin: 0 30px;
  padding: 30px;
}

.input-nickName {
  border: 1px solid #eee;
  padding: 10px;
  font-size: medium;
  width: 100%;
  margin-top: 40px;
  margin-bottom: 40px;
}

.avatar-selector {
  width: 200px;
  margin: 0 auto;
}

Реализовать выход

После входа в систему нам также нужна кнопка для выхода из системы. Создайтеsrc/components/Logout/index.jsфайл, код такой:

import Taro from '@tarojs/taro'
import { AtButton } from 'taro-ui'

export default function LoginButton(props) {
  return (
    <AtButton
      type="secondary"
      full
      loading={props.loading}
      onClick={props.handleLogout}
    >
      退出登录
    </AtButton>
  )
}

Реализовать нижний колонтитул

После того, как все подкомпоненты реализованы, давайте реализуемFooterкомпоненты. Создайтеsrc/components/Footerкаталог, в котором создатьindex.jsxа такжеindex.scss.index.jsxкод показывает, как показано ниже:

import Taro, { useState } from '@tarojs/taro'
import { View } from '@tarojs/components'
import { AtFloatLayout } from 'taro-ui'

import Logout from '../Logout'
import LoginForm from '../LoginForm'
import './index.scss'

export default function Footer(props) {
  // Login Form 登录数据
  const [formNickName, setFormNickName] = useState('')
  const [files, setFiles] = useState([])

  async function handleSubmit(e) {
    e.preventDefault()

    // 鉴权数据
    if (!formNickName || !files.length) {
      Taro.atMessage({
        type: 'error',
        message: '您还有内容没有填写!',
      })

      return
    }

    // 提示登录成功
    Taro.atMessage({
      type: 'success',
      message: '恭喜您,登录成功!',
    })

    // 缓存在 storage 里面
    const userInfo = { avatar: files[0].url, nickName: formNickName }
    await props.handleSubmit(userInfo)

    // 清空表单状态
    setFiles([])
    setFormNickName('')
  }

  return (
    <View className="mine-footer">
      {props.isLogged && (
        <Logout loading={props.isLogout} handleLogout={props.handleLogout} />
      )}
      <View className="tuture-motto">
        {props.isLogged ? 'From 图雀社区 with Love ❤' : '您还未登录'}
      </View>
      <AtFloatLayout
        isOpened={props.isOpened}
        title="登录"
        onClose={() => props.handleSetIsOpened(false)}
      >
        <LoginForm
          formNickName={formNickName}
          files={files}
          handleSubmit={e => handleSubmit(e)}
          handleNickNameInput={e => setFormNickName(e.target.value)}
          handleFilesSelect={files => setFiles(files)}
        />
      </AtFloatLayout>
    </View>
  )
}

FooterКод файла стиля компонента выглядит следующим образом:

.mine-footer {
  font-size: 28px;
  color: #777;
  margin-bottom: 20px;
}

.tuture-motto {
  margin-top: 40px;
  text-align: center;
}

После того, как все виджеты сделаны, мы находимся вsrc/componentsнужно только выставитьHeaderа такжеFooter. Исправлятьsrc/components/index.jsx, код показан ниже:

import PostCard from './PostCard'
import PostForm from './PostForm'
import Footer from './Footer'
import Header from './Header'

export { PostCard, PostForm, Footer, Header }

Обновите «Мою» страницу

Пришло время использовать написанноеHeaderа такжеFooterкомпонентов, но перед этим поговорим о том, что нам нужно использоватьuseEffectКрючки.

useEffect Hooks

useEffectХуки используются для замены функции хука жизненного цикла оригинального React.Мы можем инициировать в нем некоторые операции «побочного эффекта», такие как асинхронное получение внутренних данных, установка таймеров или выполнение операций DOM:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 和 componentDidMount 以及 componentDidUpdate 类似:
  useEffect(() => {
    // 使用浏览器 API 更新 document 的标题
    document.title = `你点击了 ${count} 次`;
  });

  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>
        点我
      </button>
    </div>
  );
}

вышеуказанная параdocumentМодификация заголовка — операция с побочными эффектами, в предыдущем React-приложении мы обычно писали:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    document.title = `你点击了 ${this.state.count} 次`;
  }

  componentDidUpdate() {
    document.title = `你点击了 ${this.state.count} 次`;
  }

  render() {
    return (
      <div>
        <p>你点击了 {this.state.count} 次</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          点我
        </button>
      </div>
    );
  }
}

Если вы хотите знатьuseEffectДля получения более подробной информации вы можете проверить Reactофициальная документация.

отличная работа! понялuseEffectПосле концепции хуков, давайте сразу обновим компонент «Моя страница»src/pages/mine/mine.jsx, код показан ниже:

import Taro, { useState, useEffect } from '@tarojs/taro'
import { View } from '@tarojs/components'

import { Header, Footer } from '../../components'
import './mine.scss'

export default function Mine() {
  const [nickName, setNickName] = useState('')
  const [avatar, setAvatar] = useState('')
  const [isOpened, setIsOpened] = useState(false)
  const [isLogout, setIsLogout] = useState(false)

  // 双取反来构造字符串对应的布尔值,用于标志此时是否用户已经登录
  const isLogged = !!nickName

  useEffect(() => {
    async function getStorage() {
      try {
        const { data } = await Taro.getStorage({ key: 'userInfo' })

        const { nickName, avatar } = data
        setAvatar(avatar)
        setNickName(nickName)
      } catch (err) {
        console.log('getStorage ERR: ', err)
      }
    }

    getStorage()
  })

  async function setLoginInfo(avatar, nickName) {
    setAvatar(avatar)
    setNickName(nickName)

    try {
      await Taro.setStorage({
        key: 'userInfo',
        data: { avatar, nickName },
      })
    } catch (err) {
      console.log('setStorage ERR: ', err)
    }
  }

  async function handleLogout() {
    setIsLogout(true)

    try {
      await Taro.removeStorage({ key: 'userInfo' })

      setAvatar('')
      setNickName('')
    } catch (err) {
      console.log('removeStorage ERR: ', err)
    }

    setIsLogout(false)
  }

  function handleSetIsOpened(isOpened) {
    setIsOpened(isOpened)
  }

  function handleClick() {
    handleSetIsOpened(true)
  }

  async function handleSubmit(userInfo) {
    // 缓存在 storage 里面
    await Taro.setStorage({ key: 'userInfo', data: userInfo })

    // 设置本地信息
    setAvatar(userInfo.avatar)
    setNickName(userInfo.nickName)

    // 关闭弹出层
    setIsOpened(false)
  }

  return (
    <View className="mine">
      <Header
        isLogged={isLogged}
        userInfo={{ avatar, nickName }}
        handleClick={handleClick}
        setLoginInfo={setLoginInfo}
      />
      <Footer
        isLogged={isLogged}
        isOpened={isOpened}
        isLogout={isLogout}
        handleLogout={handleLogout}
        handleSetIsOpened={handleSetIsOpened}
        handleSubmit={handleSubmit}
      />
    </View>
  )
}

Mine.config = {
  navigationBarTitleText: '我的',
}

Как видите, мы сделали следующее:

  • использоватьuseStateСоздаются четыре состояния: Информация о пользователе (nickNameа такжеavatar), открыт ли всплывающий слой входа в систему (isOpened), успешно ли выполнен вход (isLogged) и соответствующую функцию обновления
  • пройти черезuseEffectХук пытается получить информацию о пользователе из локального кеша (Taro.getStorage) и используется для обновленияnickNameа такжеavatarусловие
  • осознал давно потерянноеsetLoginInfoфункцию, где мы не только обновилиnickNameа такжеavatarсостояние, а также хранить пользовательские данные в локальном кеше (Taro.getStorage), убедитесь, что вы остаетесь в системе в следующий раз, когда откроете его.
  • добился того же давно потерянногоhandleLogoutфункция, которая не только обновляет актуальное состояние, но и удаляет данные в локальном кеше (Taro.removeStorage)
  • Реализовано для обработки обычных входов в системуhandleSubmitфункция, содержание в основном такое же, какsetLoginInfoпоследовательный
  • Рендеринг при возврате к коду JSXHeaderа такжеFooterКомпонент, передача в соответствующее состояние и функция обратного вызова

КорректированиеMineстиль компонентаsrc/pages/mine/mine.scssкод показывает, как показано ниже:

.mine {
  margin: 30px;
  height: 90vh;
  padding: 40px 40px 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

Наконец вsrc/app.scssВведите стили соответствующих компонентов пользовательского интерфейса Таро в:

@import './custom-theme.scss';

@import '~taro-ui/dist/style/components/button.scss';
@import '~taro-ui/dist/style/components/fab.scss';
@import '~taro-ui/dist/style/components/icon.scss';
@import '~taro-ui/dist/style/components/float-layout.scss';
@import '~taro-ui/dist/style/components/textarea.scss';
@import '~taro-ui/dist/style/components/message.scss';
@import '~taro-ui/dist/style/components/avatar.scss';
@import '~taro-ui/dist/style/components/image-picker.scss';
@import '~taro-ui/dist/style/components/icon.scss';

Посмотреть эффект

Наконец-то добрались до священной ссылки принятия. Первый — обычный логин:

Для входа в WeChat и Alipay после нажатия вы войдете в систему напрямую с учетной записью, используемой для входа в инструменты разработчика. Ниже показан интерфейс после входа в WeChat и Alipay:

После входа в систему нажмите кнопку «Выйти» ниже, чтобы выйти из текущей учетной записи.

На этом третья часть «Масштабной практики разработки мультитерминальной мини-программы Таро» завершена. В следующихГлава 4В , мы будем постепенно использовать Redux для реконструкции потока бизнес-данных, чтобы наше теперь немного раздутое управление состоянием стало понятным и контролируемым.

Хотите узнать больше интересных практических технических руководств? ПриходитьСообщество ТукеМагазин вокруг.

Исходный код, задействованный в этой статье, размещен вGithubНа, если вы считаете, что мы написали неплохо, я надеюсь, что вы можете поставить лайк этой статье ❤️ + звезда репозитория Github ❤️ о~