(十九)硅谷外卖-实现登陆注册功能

一、说明

1.1 界面相关效果

  1. 切换登陆方式
  2. 手机号合法检查
  3. 倒计时效果
  4. 切换显示或隐藏密码
  5. 前台验证提示

1.2 前后台交互功能

  1. 动态一次性图形验证码
  2. 动态一次性短信验证码
  3. 短信登陆
  4. 密码登陆
  5. 获取用户信息,实现自动登陆
  6. 退出登陆

二、vuex

2.1 state.js

userInfo: {}, // 用户信息

2.2 mutation-types.js

export const RECEIVE_USER_INFO = 'receive_user_info' // 接收用户信息
export const RESET_USER_INFO = 'reset_user_info' // 重置用户信息

2.3 mutations.js

[RESET_USER_INFO](state) {
    state.userInfo = {}
},
[RECEIVE_USER_INFO](state, {userInfo}) {
    state.userInfo = userInfo
}

2.4 actions.js

// 记录用户信息
recordUserInfo({commit}, userInfo) {
    commit(RECEIVE_USER_INFO, {userInfo: userInfo})
},

// 异步获取用户信息
async getUserInfo({commit}) {
    const result = await reqUser()
    if (result.code === 0) {
        commit(RECEIVE_USER_INFO, {UserInfo: result.data})
    }
},

// 退出登陆
async logout({commit}) {
    const result = await reqLogout()
    if (result.code === 0) {
        commit(RESET_USER_INFO)
    }
}

三、Msite.vue

<template>
  <section class="msite">
    <!--首页头部-->
    <HeaderTop :title="address.name">
      <router-link slot="search" to="/search" class="header_search">
        <i class="iconfont icon-sousuo"></i>
      </router-link>
      <router-link slot="login" :to="userInfo._id ? '/userinfo' : '/login'" class="header_login">
        <span class="header_login_text" v-if="!userInfo._id">登录|注册</span>
        <span class="header_login_text" v-else>
          <i class="iconfont icon-person"></i>
        </span>
      </router-link>
    </HeaderTop>
    <!--首页导航-->
    <nav class="msite_nav">
      <div class="swiper-container" v-if="categorysArr.length > 0">
        <div class="swiper-wrapper">
          <div class="swiper-slide" v-for="(cs, index) in categorysArr" :key="index">
            <a href="javascript:" class="link_to_food" v-for="(c, index2) in cs" :key="index2">
              <div class="food_container">
                <img :src="imgBaseUrl + c.image_url">
              </div>
              <span>{{c.title}}</span>
            </a>
          </div>
        </div>
        <!-- Add Pagination -->
        <div class="swiper-pagination"></div>
      </div>
      <img src="./images/msite_back.svg" v-else>
    </nav>
    <!--首页附近商家-->
    <div class="msite_shop_list">
      <div class="shop_header">
        <i class="iconfont icon-xuanxiang"></i>
        <span class="shop_header_title">附近商家</span>
      </div>
      <ShopList/>
    </div>
  </section>
</template>

<script>
  import Swiper from 'swiper'
  import 'swiper/dist/css/swiper.min.css'
  import HeaderTop from '../../components/HeaderTop/HeaderTop'
  import ShopList from '../../components/ShopList/ShopList'
  import {mapState} from 'vuex'

  export default {
    name: 'Msite',
    data () {
        return {
                imgBaseUrl: 'https://fuss10.elemecdn.com'
      }
    },
    mounted() {
      this.$store.dispatch('getShops')
      this.$store.dispatch('getCategorys')
        },
        computed: {
      ...mapState(['address', 'categorys', 'userInfo']),

            categorysArr () {
                const max = 8
                const arr = []
                const {categorys} = this
                let smallArr = []
                categorys.forEach((c, index) => {
                    if(smallArr.length === 0) {
                        arr.push(smallArr)
                    }
                    smallArr.push(c)
                    if(smallArr.length === max) {
                        smallArr = []
                    }
                })
                return arr
            }
    },
    components: {
        HeaderTop,
      ShopList
    },
    watch: {
        categorys(value) {
            this.$nextTick(() => {
                    new Swiper('.swiper-container', {
                        pagination: {
                            el: '.swiper-pagination'
                        },
                        loop: true
                    })
        })
      }
    }
    }
</script>

<style lang="stylus" rel="stylesheet/stylus">
  @import "../../common/stylus/mixins.styl"
  &.msite  //首页
    width 100%
    .msite_nav
      bottom-border-1px(#e4e4e4)
      margin-top 45px
      height 200px
      background #fff
      .swiper-container
        width 100%
        height 100%
        .swiper-wrapper
          width 100%
          height 100%
          .swiper-slide
            display flex
            justify-content center
            align-items flex-start
            flex-wrap wrap
            .link_to_food
              width 25%
              .food_container
                display block
                width 100%
                text-align center
                padding-bottom 10px
                font-size 0
                img
                  display inline-block
                  width 50px
                  height 50px
              span
                display block
                width 100%
                text-align center
                font-size 13px
                color #666
        .swiper-pagination
          >span.swiper-pagination-bullet-active
            background #02a774
    .msite_shop_list
      top-border-1px(#e4e4e4)
      margin-top 10px
      background #fff
      .shop_header
        padding 10px 10px 0
        .shop_icon
          margin-left 5px
          color #999
        .shop_header_title
          color #999
          font-size 14px
          line-height 20px
</style>

四、Profile.vue

<template>
  <section class="profile">
    <HeaderTop title="我的"></HeaderTop>
    <section class="profile-number">
      <router-link :to="userInfo._id ? '/userinfo' : '/login'" class="profile-link">
        <div class="profile_image">
          <i class="iconfont icon-person"></i>
        </div>
        <div class="user-info">
          <p class="user-info-top" v-if="!userInfo.phone">{{userInfo.name || '登录/注册'}}</p>
          <p>
            <span class="user-icon">
              <i class="iconfont icon-shouji icon-mobile"></i>
            </span>
            <span class="icon-mobile-number"> {{userInfo.phone || '暂无绑定手机号'}}</span>
          </p>
        </div>
        <span class="arrow">
          <i class="iconfont icon-jiantou1"></i>
        </span>
      </router-link>
    </section>
    <section class="profile_info_data border-1px">
      <ul class="info_data_list">
        <a href="javascript:" class="info_data_link">
          <span class="info_data_top"><span>0.00</span></span>
          <span class="info_data_bottom">我的余额</span>
        </a>
        <a href="javascript:" class="info_data_link">
          <span class="info_data_top"><span>0</span></span>
          <span class="info_data_bottom">我的优惠</span>
        </a>
        <a href="javascript:" class="info_data_link">
          <span class="info_data_top"><span>0</span></span>
          <span class="info_data_bottom">我的积分</span>
        </a>
      </ul>
    </section>
    <section class="profile_my_order border-1px">
      <!-- 我的订单 -->
      <a href='javascript:' class="my_order">
            <span>
              <i class="iconfont icon-order-s"></i>
            </span>
        <div class="my_order_div">
          <span>我的订单</span>
          <span class="my_order_icon">
                <i class="iconfont icon-jiantou1"></i>
              </span>
        </div>
      </a>
      <!-- 积分商城 -->
      <a href='javascript:' class="my_order">
            <span>
              <i class="iconfont icon-jifen"></i>
            </span>
        <div class="my_order_div">
          <span>积分商城</span>
          <span class="my_order_icon">
                <i class="iconfont icon-jiantou1"></i>
              </span>
        </div>
      </a>
      <!-- 硅谷外卖会员卡 -->
      <a href="javascript:" class="my_order">
            <span>
              <i class="iconfont icon-vip"></i>
            </span>
        <div class="my_order_div">
          <span>硅谷外卖会员卡</span>
          <span class="my_order_icon">
                <i class="iconfont icon-jiantou1"></i>
              </span>
        </div>
      </a>
    </section>
    <section class="profile_my_order border-1px">
      <!-- 服务中心 -->
      <a href="javascript:" class="my_order">
            <span>
              <i class="iconfont icon-fuwu"></i>
            </span>
        <div class="my_order_div">
          <span>服务中心</span>
          <span class="my_order_icon">
                <i class="iconfont icon-jiantou1"></i>
              </span>
        </div>
      </a>
    </section>
    <section class="profile_my_order border-1px" v-if="userInfo._id">
      <mt-button type="danger" style="width: 100%" @click="logout">退出登录</mt-button>
    </section>
  </section>
</template>

<script>
    import HeaderTop from '../../components/HeaderTop/HeaderTop'
  import {mapState} from 'vuex'
    import { MessageBox} from 'mint-ui';

  export default {
    name: 'Profile',
    components: {
        HeaderTop
    },
    computed: {
        ...mapState(['userInfo'])
    },
    methods: {
            logout () {
                MessageBox.confirm('确定退出登陆吗?').then(action => {
                    this.$store.dispatch('logout')
        })
      }
    }
  }
</script>

<style lang="stylus" rel="stylesheet/stylus">
  @import "../../common/stylus/mixins.styl"
  &.profile //我的
    width 100%
    .profile-number
      margin-top 45.5px
      .profile-link
        clearFix()
        position relative
        display block
        background #02a774
        padding 20px 10px
        .profile_image
          float left
          width 60px
          height 60px
          border-radius 50%
          overflow hidden
          vertical-align top
          .icon-person
            background #e4e4e4
            font-size 62px
        .user-info
          float left
          margin-top 8px
          margin-left 15px
          p
            font-weight: 700
            font-size 18px
            color #fff
            &.user-info-top
              padding-bottom 8px
            .user-icon
              display inline-block
              margin-left -15px
              margin-right 5px
              width 20px
              height 20px
              .icon-mobile
                font-size 30px
                vertical-align text-top
            .icon-mobile-number
              font-size 14px
              color #fff
        .arrow
          width 12px
          height 12px
          position absolute
          right 15px
          top 40%
          .icon-jiantou1
            color #fff
            font-size 5px
    .profile_info_data
      bottom-border-1px(#e4e4e4)
      width 100%
      background #fff
      overflow hidden
      .info_data_list
        clearFix()
        .info_data_link
          float left
          width 33%
          text-align center
          border-right 1px solid #f1f1f1
          .info_data_top
            display block
            width 100%
            font-size 14px
            color #333
            padding 15px 5px 10px
            span
              display inline-block
              font-size 30px
              color #f90
              font-weight 700
              line-height 30px
          .info_data_bottom
            display inline-block
            font-size 14px
            color #666
            font-weight 400
            padding-bottom 10px
        .info_data_link:nth-of-type(2)
          .info_data_top
            span
              color #ff5f3e
        .info_data_link:nth-of-type(3)
          border 0
          .info_data_top
            span
              color #6ac20b
    .profile_my_order
      top-border-1px(#e4e4e4)
      margin-top 10px
      background #fff
      .my_order
        display flex
        align-items center
        padding-left 15px
        >span
          display flex
          align-items center
          width 20px
          height 20px
          >.iconfont
            margin-left -10px
            font-size 30px
          .icon-order-s
            color #02a774
          .icon-jifen
            color #ff5f3e
          .icon-vip
            color #f90
          .icon-fuwu
            color #02a774
        .my_order_div
          width 100%
          border-bottom 1px solid #f1f1f1
          padding 18px 10px 18px 0
          font-size 16px
          color #333
          display flex
          justify-content space-between
          span
            display block
          .my_order_icon
            width 10px
            height 10px
            .icon-jiantou1
              color #bbb
              font-size 10px
</style>

五、提示框组件: components/AlertTip/AlertTip.vue

<template>
  <div class="alert_container">
    <section class="tip_text_container">
      <div class="tip_icon">
        <span></span>
        <span></span>
      </div>
      <p class="tip_text">{{alertText}}</p>
      <div class="confrim" @click="closeTip">确认</div>
    </section>
  </div>
</template>

<script>
    export default {
        props: {
            alertText: String
        },
        methods: {
            closeTip() {
                this.$emit('closeTip')
            }
        }
    }
</script>

<style lang="stylus" rel="stylesheet/stylus">
  @import '../../common/stylus/mixins.styl';

  @keyframes tipMove
    0%
      transform: scale(1)
    35%
      transform: scale(.8)
    70%
      transform: scale(1.1)
    100%
      transform: scale(1)

  .alert_container
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 200;
    background: rgba(0, 0, 0, .5)
    .tip_text_container
      position: absolute;
      top: 50%;
      left: 50%;
      margin-top: -90px
      margin-left: -110px
      width: 60%
      animation: tipMove .4s;
      background-color: rgba(255, 255, 255, 1);
      border: 1px;
      padding-top: 20px
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      border-radius: 5px
      .tip_icon
        width: 55px
        height: 55px
        border: 2px solid #f8cb86;
        border-radius: 50%;
        font-size 20px
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        span:nth-of-type(1)
          width: 2px
          height: 30px
          background-color: #f8cb86;
        span:nth-of-type(2)
          width: 2px
          height: 2px
          border: 1px;
          border-radius: 50%;
          margin-top: 2px
          background-color #f8cb86
      .tip_text
        font-size 14px
        color #333
        line-height 20px
        text-align center
        margin-top 10px
        padding 0 5px
      .confrim
        font-size 18px
        font-weight bold
        margin-top 10px
        background-color #4cd964
        width 100%
        text-align center
        line-height 35px
        border 1px
        color #fff
        border-bottom-left-radius 5px
        border-bottom-right-radius 5px
</style>

六、使用mint-ui

6.1 主页

http://mint-ui.github.io/#!/zh-cn

6.2 下载

npm install --save mint-ui

6.3 实现按需打包

  1. 下载:npm install --save-dev babel-plugin-component
  2. 修改 
    plugins:[
        [
          "component",
          {
            "libraryName": "mint-ui",
            "style": true
          }
        ]
    ]

6.4 mint-ui 组件分类

  1. 标签组件
  2. 非标签组件

七、使用 mint-ui 的组件

7.1 mian.js

import {
  Button,
} from 'mint-ui'

// 注册全局组件 Vue.component(Button.name, Button)

7.2 Profile.vue

<section class="profile_my_order border-1px" v-if="userInfo._id">
      <mt-button type="danger" style="width: 100%" @click="logout">退出登录</mt-button>
</section>

<script>
  import { MessageBox} from 'mint-ui';

  export default {
    name: 'Profile',
    computed: {
        ...mapState(['userInfo'])
    },
    methods: {
    logout () {
        MessageBox.confirm('确定退出登陆吗?').then(action => {
            this.$store.dispatch('logout')
                })
        }
    }
  }
</script>        

八、Login.vue

<template>
  <section class="loginContainer">
    <div class="loginInner">
      <div class="login_header">
        <h2 class="login_logo">硅谷外卖</h2>
        <div class="login_header_title">
          <a href="javascript:;" :class="{on: !loginWay}" @click="loginWay=false">短信登录</a>
          <a href="javascript:;" :class="{on: loginWay}" @click="loginWay=true">密码登录</a>
        </div>
      </div>
      <div class="login_content">
        <form @submit.prevent="login">
          <div :class="{on: !loginWay}">
            <section class="login_message">
              <input type="tel" maxlength="11" placeholder="手机号" v-model="phone">
              <button :disabled="!rightPhone" class="get_verification" :class="{rightPhone: rightPhone}" @click.preven="getCode">
                {{computeTime > 0 ? computeTime + 'S' : "获取验证码"}}
              </button>
            </section>
            <section class="login_verification">
              <input type="tel" maxlength="8" placeholder="验证码">
            </section>
            <section class="login_hint">
              温馨提示:未注册硅谷外卖帐号的手机号,登录时将自动注册,且代表已同意
              <a href="javascript:;">《用户服务协议》</a>
            </section>
          </div>
          <div :class="{on: loginWay}">
            <section>
              <section class="login_message">
                <input type="tel" maxlength="11" placeholder="手机/邮箱/用户名" v-model="name">
              </section>
              <section class="login_verification">
                <input type="text" maxlength="8" placeholder="密码" v-model="pwd" v-if="showPassword">
                <input type="password" maxlength="8" placeholder="密码" v-model="pwd" v-if="!showPassword">
                <div class="switch_button" :class="showPassword ? 'on' : 'off'" @click="showPassword=!showPassword">
                  <div class="switch_circle" :class="{right: showPassword}" :style="{transform: showPassword ? 'translateX(27px)' : 'translateX(0px)'}"></div>
                  <span class="switch_text">{{showPassword ? 'abc' : '...'}}</span>
                </div>
              </section>
              <section class="login_message">
                <input type="text" maxlength="11" placeholder="验证码" v-model="captcha">
                <img class="get_verification" src="http://localhost:4000/captcha" @click="getCaptcha" ref="captcha">
              </section>
            </section>
          </div>
          <button class="login_submit">登录</button>
        </form>
        <a href="javascript:;" class="about_us">关于我们</a>
      </div>
      <a href="javascript:" @click="$router.back()" class="go_back">
        <i class="iconfont icon-jiantou2"></i>
      </a>
    </div>
    <AlertTip :alert-text="alertText" v-show="showAlert" @closeTip="closeTip"></AlertTip>
  </section>
</template>

<script>
  import {reqPwdLogin, reqSendCode, reqSmsLogin} from "../../api"
  import AlertTip from '../../components/AlertTip/AlertTip'

    export default {
    name: 'Login',
    data() {
        return {
                loginWay: true, // true 代表密码登陆, false 代表短信登陆
                computeTime: 0,
                showPassword: false, // 是否显示密码
                phone: '', // 手机号
                code: '', // 短信验证码
                name: '', // 用户名
                pwd: '', // 密码
                captcha: '', // 图形验证码
                showAlert: false, // 是否显示提示框
                alertText: '', // 提示框文本
      }
    },
    mounted() {
        this.name = ''
      this.pwd = ''
        },
    computed: {
        rightPhone() {
            return /^1\d{10}$/.test(this.phone)
      }
    },
    components: {
        AlertTip
    },
    methods: {
        // 获取短信验证码
      async getCode() {
          if (this.computeTime === 0) {
          this.computeTime = 60
          this.intervalId = setInterval(() => {
              this.computeTime--
            if (this.computeTime === 0) {
                clearInterval(this.intervalId)
            }
          }, 1000)

                    // 发送短信验证码
          let result = await reqSendCode(this.phone)
          if (result.code === 1) {
              // 显示提示框
              this.showAlert = true
            this.alertText = result.msg
            // 停止倒计时
            if (this.computeTime) {
                this.computeTime = 0
              clearInterval(this.intervalId)
            }
          }
        }
      },

      // 获取图形验证码
      getCaptcha() {
          this.$refs.captcha.src = 'http://localhost:4000/captcha?time=' + Date.now()
      },

      // 关闭提示框
      closeTip() {
          this.showAlert = false
        this.alertText = ''
      },

      // 发送登录信息
      async login() {
          if (!this.loginWay) {
              if (!this.rightPhone) {
                  this.showAlert = true
            this.alertText = '手机号码不正确'
            return
          } else if (!(/^\d{6}&/gi.test(this.code))) {
            this.showAlert = true
            this.alertText = '短信验证码不正确'
            return
          }

                    // 手机号短信登录
                    const result = await reqSmsLogin(this.phone, this.code)
                    if (result.code === 0) {
                        this.userInfo = result.data
          } else {
                        this.userInfo = {
                            msg: '登录失败, 手机号或验证码不正确'
            }
          }
        } else {
              if (!this.name) {
                  this.showAlert = true
            this.alertText = '请输入手机号/邮箱/用户名'
            return
          } else if (!this.pwd) {
                  this.showAlert = true
            this.alertText = '请输入密码'
            return
          } else if (!this.captcha) {
                  this.showAlert = true
            this.alertText = '请输入验证码'
            return
          }

              // 用户名登录
          const result = await reqPwdLogin(this.name, this.pwd, this.captcha)
          if (result.code === 0) {
              this.userInfo = result.data
                        this.$store.dispatch('recordUserInfo', this.userInfo)
                        this.$router.push('/profile')
          } else {
                        this.showAlert = true
                        this.alertText = result.msg
            this.getCaptcha()
            this.captcha = ''
          }
        }
      },
    }
    }
</script>

<style lang="stylus" rel="stylesheet/stylus">
  .loginContainer
    width 100%
    height 100%
    background #fff
    .loginInner
      padding-top 60px
      width 80%
      margin 0 auto
      .login_header
        .login_logo
          font-size 40px
          font-weight bold
          color #02a774
          text-align center
        .login_header_title
          padding-top 40px
          text-align center
          >a
            color #333
            font-size 14px
            padding-bottom 4px
            &:first-child
              margin-right 40px
            &.on
              color #02a774
              font-weight 700
              border-bottom 2px solid #02a774
      .login_content
        >form
          >div
            display none
            &.on
              display block
            input
              width 100%
              height 100%
              padding-left 10px
              box-sizing border-box
              border 1px solid #ddd
              border-radius 4px
              outline 0
              font 400 14px Arial
              &:focus
                border 1px solid #02a774
            .login_message
              position relative
              margin-top 16px
              height 48px
              font-size 14px
              background #fff
              .get_verification
                position absolute
                top 50%
                right 10px
                transform translateY(-50%)
                border 0
                color #ccc
                font-size 14px
                background transparent
                &.rightPhone
                  color black
            .login_verification
              position relative
              margin-top 16px
              height 48px
              font-size 14px
              background #fff
              .switch_button
                font-size 12px
                border 1px solid #ddd
                border-radius 8px
                transition background-color .3s,border-color .3s
                padding 0 6px
                width 30px
                height 16px
                line-height 16px
                color #fff
                position absolute
                top 50%
                right 10px
                transform translateY(-50%)
                &.off
                  background #fff
                  .switch_text
                    float right
                    color #ddd
                &.on
                  background #02a774
                >.switch_circle
                  //transform translateX(27px)
                  position absolute
                  top -1px
                  left -1px
                  width 16px
                  height 16px
                  border 1px solid #ddd
                  border-radius 50%
                  background #fff
                  box-shadow 0 2px 4px 0 rgba(0,0,0,.1)
                  transition transform .3s
            .login_hint
              margin-top 12px
              color #999
              font-size 14px
              line-height 20px
              >a
                color #02a774
          .login_submit
            display block
            width 100%
            height 42px
            margin-top 30px
            border-radius 4px
            background #4cd96f
            color #fff
            text-align center
            font-size 16px
            line-height 42px
            border 0
        .about_us
          display block
          font-size 12px
          margin-top 20px
          text-align center
          color #999
      .go_back
        position absolute
        top 5px
        left 5px
        width 30px
        height 30px
        >.iconfont
          font-size 20px
          color #999
</style>

猜你喜欢

转载自www.cnblogs.com/mxsf/p/10993364.html