0
点赞
收藏
分享

微信扫一扫

项目分析二

互联网码农 2022-03-12 阅读 58
前端

一、表单增加验让规则

  • 在vant组件库中找到Form表单验证规则

:rules="[{ validator: asyncValidator, message: '请输入正确内容' }]"
  • 可以对其进行封闭,方便管理使用
data() {
    return {
      ......
      userForRules: {
        mobile: [
          {
            required: true,
            message: "手机号不能为空",
          },
          {
            pattern: /^((13[0-9])|(17[0-1,6-8])|(15[^4,\\D])|(18[0-9]))\d{8}$/,
            message:'手机号格式错误'
          },
        ],
        code: [
          {
            required: true,
            message: "验证码不能为空",
          },
          {
            pattern: /^\d{6}$/,
            message:'验证码格式错误'
          },
        ],
      },
    };
  },
  • 在表结构中放入
    <van-form @submit="onSubmit">
      <van-field
        name="mobile"
        placeholder="请输入手机号"
        v-model="user.mobile"   // 调用上面的对象
        :rules="userForRules.mobile"
        type="number"  // 规定表单中只能输入数定,其他类型的不会显示,这样体验效果会更好
        maxlength="11"  // 规定了最大长度,超过不会再显示
      >
        ......
      </van-field>

二、实现发送验证码功能

  • 校验手机号
<van-form @submit="onSubmit" ref="LoginForm"> // 1.给表单增加ref属性
.....
<template #button>
          <van-button
            round
            size="small"
            type="default"
            native-type="button"  // 3.button的默认值是submit,点击会触发表单提交,所以需要修改默认值,改为native-type="button"
            class="buttonss"   
            @click="onSendSms"  // 2.先绑定点击事件
            >发送验证码</van-button
          >
        </template>
      </van-field>
</van-form>
 
 // 4.点击校验
 methods: {
 .....
  async onSendSms(){
      try{
        await this.$refs.LoginForm.validate('mobile') // validate验证表单,支持传入 name 来验证单个或部分表单项
      } catch(err){
        return console.log('手机号验证失败'); // 如果验证失败不会触发下面的内容了
      }
      console.log(2222);
    }
  }
  • 验证通过,显示倒计时
// 1.在vant中有CountDown 倒计时 组件
// 2.使用CountDown 倒计时 组件
<template #button>
	<van-count-down :time="time" format="ss s" v-if="isCountDownShow" @finish="isCountDownShow=false" />
	// 先进行判断  @finish->倒计时结束时触发	
          <van-button
          v-else
          ....></van-button>
        </template>
      </van-field>
      
   data() {
    return {
      .......
      time:1000 * 6,
      isCountDownShow: false,
    };
  },
   methods: {
    ....
    async onSendSms() {
      try ...
      console.log(2222);
      this.isCountDownShow = true
    },
  },
  • 请求发送验证码
// 先进行封装,在src/api/user.js
export const sendSms = (mobile) => {
  return request({
    method: 'GET',
    url: `/v1_0/sms/codes/${mobile}`,  //这里使用模版字符串拼接,把数据拼接上了
  })
}
// 引入
import { login,sendSms } from "@/api/user";
// 判断
methods: {
    ......
      console.log(2222);
      this.isCountDownShow = true
      try{
        await sendSms(this.user.mobile)
        this.$toast('发送成功');
      } catch(err){
        this.isCountDownShow = false
        if(err.response.status === 429){
           this.$toast('发送频繁');
        } else{
           this.$toast('发送失败');
        }
      }
    },
  },

三、处理用户Token

  • 在src/store/index.js 中

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const TOKEN_KEY = 'TOUTIAO_USER'

export default new Vuex.Store({
  state: {
    // user:null
    // 一个对象,存储当前登录用户信息(token等数据)
    user: JSON.parse(window.localStorage.getItem(TOKEN_KEY))
  },
  mutations: {
    setUser (state, data) {
      state.user = data
      // 为了防止刷新丢失,我们需要把数据备份到本地存储
      window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user))
    }
  },
  actions: {
  },
  modules: {
  }
})
  • src/views/login/index.vue 中
<van-button
            v-else
            round
            size="small"
            type="default"
            native-type="button"
            class="buttonss"
            @click="onSendSms" // 绑定事件
            >发送验证码
</van-button>

methods: {
    async onSubmit(values) {
      const user = this.user;
      this.$toast.loading({
        message: "加载中...",
        forbidClick: true,
        duration: 0,
      });
      try {
        const { data } = await login(user);
        console.log(data);
        this.$toast.success("登录成功");
        // 将用户数据存储到vuex中
        // this.$store.commit('')
        this.$store.commit("setUser", data.data);
      } catch (error) {
        if (error.response.status === 400) {
          console.log("用户或者密码错误");
          this.$toast.fail("用户或者密码错误");
        } else {
          console.log("未知的错误");
          this.$toast.fail("登录失败,请稍后重试");
        }
      }
      // console.log("submit", values);
    },
  .......  
},
  • 优化
// 创建文件 src/utils/storage.js
// 获取本地存储的数据
export const getItem = (key) =>{
    const data = window.localStorage.getItem(key)
    try{
        return JSON.parse(data)
    } catch(err){
        return data
    }
}

// 存储数据
export const setItem = (key,data) =>{
    if(typeof data === 'object'){
        data = JSON.stringify(data)
    }
    window.localStorage.setItem(key,data)
}

// 删除数据
export const reomveItem = (key) =>{
    window.localStorage.removeItem(key)
}


// 在src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

import {getItem,setItem} from '@/utils/storage'

Vue.use(Vuex)

const TOKEN_KEY = 'TOUTIAO_USER'

export default new Vuex.Store({
  state: {
    // user:null
    // 一个对象,存储当前登录用户信息(token等数据)
    user:getItem(TOKEN_KEY)
  },
  mutations: {
    setUser (state, data) {
      state.user = data
      // 为了防止刷新丢失,我们需要把数据备份到本地存储
      setItem(TOKEN_KEY,data)
    }
  },
  actions: {
  },
  modules: {
  }
})

四、会员中心——创建主页面

  • 创建src/layout/index.vue

  • 创建src/home/index.vue

  • 创建src/video/index.vue

  • 创建src/my/index.vue

  • 创建src/qa/index.vue

  • 如下图所示:

  • 在router/index.js中添加路由
const routes = [
  ......
  {
    path: "/",
    component: () => import("@/views/layout"),
    children: [  // 注意嵌套关系,容易混乱
      {
        path: "",
        name: "Home",
        component: () => import("@/views/home"),
      },
      {
        path: "video",
        name: "video",
        component: () => import("@/views/video"),
      },
      {
        path: "my",
        name: "my",
        component: () => import("@/views/my"),
      },
      {
        path: "qa",
        name: "qa",
        component: () => import("@/views/qa"),
      },
    ]
    
  },
];
  •  在src/layout/index.vue文件中(使用Tabbar 标签栏组件中的路由模式,图标使用自定义图标):
<template>
  <div class="layout-container">
    <!-- <router-view></router-view>  用单标签和双标签都可以-->
    <router-view />
    <van-tabbar route>
      <van-tabbar-item replace to="/">
        <template #icon>
          <i class="toutiao toutiao-shouye"></i> // 自定义图标
        </template>
        <span class="text">首页</span> 
      </van-tabbar-item>

      <van-tabbar-item replace to="/qa">
        <template #icon>
          <i class="toutiao toutiao-wenda"></i>
        </template>
        <span class="text">问答</span>
      </van-tabbar-item>

      <van-tabbar-item replace to="/video">
        <template #icon>
          <i class="toutiao toutiao-shipin"></i>
        </template>
        <span class="text">视频</span>
      </van-tabbar-item>

      <van-tabbar-item replace to="/my">
        <template #icon>
          <i class="toutiao toutiao-wode"></i>
        </template>
        <span class="text">我的</span>
      </van-tabbar-item>
    </van-tabbar>
  </div>
</template>

// 修改样式
<style lang="less" scoped>
.layout-container{
  i.toutiao{  // 用的是交集选择器
    font-size: 40px;
  }
  span.text{
    font-size: 20px;
    // color: #666;
  }
}
</style>

五、我的(src/my/index.vue)页面布局

  • 写结构

<template>
  <div class="my-container">
    <div class="header not-login">
    // @click="$router.push('/login')"点击跳转到登录页面
      <div class="login-btn" @click="$router.push('/login')"> 
        <img class="mobile-img" src="~@/assets/mobile.png" />
        <span class="text">登录&nbsp;/&nbsp;注册</span>
      </div>
    </div>
  </div>
</template>
// 修改样式
.my-container {
  .header {
    height: 361px;
    background: url("~@/assets/banner.png"); // css路径要用~@,不能直接用@,会报错
    background-size: cover;
  }
  .not-login {
    display: flex;
    justify-content: center;
    align-items: center;
    .login-btn {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      .mobile-img {
        width: 132px;
        height: 132px;
        margin-bottom: 15px;
      }
      .text {
        font-size: 28px;
        color: #fff;
      }
    }
  }
}
  • 点击跳转到login页面上
  // src/login/index.vue
   <!-- 导航栏 -->
    <van-nav-bar class="page-nav-bar" title="登录">
      <template #left>
        <!-- 点击后退上一页:  $router.back() 或 $router.go(-1) -->
        <van-icon name="cross" @click="$router.back()" />
      </template>
    </van-nav-bar>
    
    // 在styles/index.less中添加全局的图标颜色
.page-nav-bar {
    .....
    .van-icon {
        color: #fff !important;
    }
}
  • 已登录结构以及样式,用vant组件搭配自己写的结构
<template>
  <div class="my-container">
    <!-- 已登录 -->
    <div class="header user-info" v-if="user">// 判断
      <!-- 用户信息 -->
      <div class="base-info">
        <div class="left">
          <van-image class="avatar"
                     round
                     fit="cover"
                     src="https://img01.yzcdn.cn/vant/cat.jpeg"
                     />
          <span class="name">黑马1号帅哥</span>
        </div>
        <div class="right">
           <van-button size="mini" round>编辑资料</van-button>
        </div>
      </div>
      <!-- 用户数据 -->
      <div class="data-stats">
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">头条</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">关注</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">粉丝</span>
        </div>
        <div class="data-item">
          <span class="count">10</span>
          <span class="text">获赞</span>
        </div>
      </div>
    </div>
            <!-- 未登录 -->
    <div class="header not-login" v-else>// 判断
     <div class="login-btn" @click="$router.push('/login')">
        <img class="mobile-img" src="~@/assets/mobile.png">
        <span class="text">登录&nbsp;/&nbsp;注册</span>
     </div>
    </div>
	<!-- 宫格导航Grid -->
	//column-num自定义列数,因为我们这里只需要2列,所以就是:column-num="2"
	//clickable 是否开启格子点击反馈
    <van-grid class="grid-nav mb-9" :column-num="2" clickable>
    //使用插槽
      <van-grid-item class="grid-item"> 
     // 这里也可以用其他方式去写:<i slot="icon" class="toutiao toutiao-shoucang"></i>  <span slot="text" class="text">收藏</span> 这样写代码简洁,要记得要slot,这样就不用写template
        <template #icon>
          <i class="toutiao toutiao-shoucang"></i>
        </template>
        <template #text>
          <span class="text">收藏</span>
        </template>
      </van-grid-item>
      <van-grid-item class="grid-item">
        <template #icon>
          <i class="toutiao toutiao-lishi"></i>
        </template>
        <template #text>
          <span class="text">历史</span>
        </template>
      </van-grid-item>
    </van-grid>
    <!-- /宫格导航 -->
    <!-- cell单元格导航 -->
    // is-link 是否展示右侧箭头并开启点击反馈
    <van-cell title="消息通知" is-link center/>
    <van-cell class="mb-9" title="小智同学" is-link center/>
    <van-cell  class="logout-cell"
              clickable
              title="退出登录" 
              center 
              v-if="user" // 判断
              @click="fnLoginOut" />  // 添加点击事件
  </div>
</template>

<script>
import {mapState} from 'vuex' //引入
export default {
  name: "MyIndex",
  data() {
    return {};
  },
  created() {},
  methods: {
  fnLoginOut() {
      // 退出提示
      // 在组件中需要使用 this.$dialog 来调用弹框组件
      this.$dialog
        .confirm({
          title: "退出登录",
          message: "确认退出登录吗?",
        })
        .then(() => {
          console.log(1111);
          // 删除veux容器的数据和本地存储的数据
          this.$store.commit("setUser", null);
        })
        .catch(() => {});
    },
  },
  components: {},
  computed: {
    ...mapState(['user']) // 使用
  }
};
</script>
// 样式
<style scoped lang="less">
.my-container {
  ....
  .user-info {
    .base-info {
      height: 231px;
      padding: 76px 32px 23px;
      box-sizing: border-box;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .left {
        display: flex;
        align-items: center;
        .avatar {
          width: 132px;
          height: 132px;
          border: 4px solid #fff;
          margin-right: 23px;
        }
        .name {
          font-size: 30px;
          color: #fff;
        }
      }
    }
    .data-stats {
      display: flex;
      .data-item {
        height: 130px;
        flex: 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        color: #fff;
        .count {
          font-size: 36px;
        }
        .text {
          font-size: 23px;
        }
      }
    }
  }

  .grid-nav {
    .grid-item {
      height: 141px;
      i.toutiao {
        font-size: 45px;
      }
      .toutiao-shoucang {
        color: #eb5253;
      }
      .toutiao-lishi {
        color: #ff9d1d;
      }
      span.text {
        font-size: 28px;
      }
    }
  }

  .logout-cell {
    text-align: center;
    color: #d86262;
  }

  .mb-9 {
    margin-bottom: 9px;
  }
}
</style>

// src/login/index.vue 中
try {
        ...
        this.$toast.success("登录成功");
        this.$router.push('/')  // 添加这个,登录成功后跳转到首页,如果想要看我的页面,就把路径改成.push('/my')就可以了
        ...

六、在我的页面中展示用户的各种信息

  • 因为我的页面中的数据都请求来的,所以先看接口文档,看哪些是需要的,再接着封闭请求

// 在src/api/user.js中封装请求
// 可以在这里先打印 ,但是如果需要打印,要在 src/views/my/index.vue中引入 import '@/api/user' ,引入后才能看到打印的结果,有打印结果后,再接着往下写
// console.log(store.state.user.data.toke);
export const getUserInfo = () => {
  return request({
    method: 'GET',
    url: '/v1_0/user',
     headers:{
       Authorization:`Bearer ${store.state.user.token}`
     }
  })
};


// src/views/my/index.vue定义变量,用于存储数据
  data() {
    return {
      userInfo:{}
    };
  },
  
  // 导入
  import { getUserInfo } from "@/api/user";
  
  // 封闭获取个人信息的函数
  methods:{
  .....
  async loadUserInfo(){
      try{
        const {data} = await getUserInfo()
        console.log(data.data);
        this.userInfo = data.data
      } catch(error){}
    }
  }
 
     // 一打开页面就触发获取个人用户信息,也可以在mouted中写,但是大多都在created里写
  created() {
    this.loadUserInfo()
  },
  
  // 渲染
  
<!-- 已登录 -->
    <div class="header user-info" v-if="user">
      <!-- 用户信息 -->
      <div class="base-info">
        <div class="left">
          <van-image
            class="avatar"
            round
            fit="cover"
            :src="userInfo.photo"  // 这里是用户的头像
          />
          <span class="name">
          {{userInfo.name}}// 这里是用户的名字
          </span>
        </div>
        .....
      </div>
      <!-- 用户数据 -->
      <div class="data-stats">
        <div class="data-item">
          <span class="count">
          {{userInfo.art_count}} // 这里是用户的头条数量
          </span>
          <span class="text">头条</span>
        </div>
        <div class="data-item">
          <span class="count">
          {{userInfo.follow_count}} // 这里是用户的关注数量
          </span>
          <span class="text">关注</span>
        </div>
        <div class="data-item">
          <span class="count">
          {{userInfo.fans_count}} // 这里是用户的粉丝数量
          </span>
          <span class="text">粉丝</span>
        </div>
        <div class="data-item">
          <span class="count">
          {{userInfo.like_count}} // 这里是用户的获赞数量
          </span>
          <span class="text">获赞</span>
        </div>
      </div>
    </div>

七、优化Token

// 将src/api/user.js中的headers去掉
export const getUserInfo = () => {
  return request({
    method: 'GET',
    url: '/v1_0/user',
    // headers:{
    //   Authorization:`Bearer ${store.state.user.token}`
    // }   
  })
};

// 然后再src/utils/request.js中添加
// 添加请求拦截器
request.interceptors.request.use(
  function (config) {
  // 在发送请求之前做些什么
  // config :本地请求对象,本次请求的配置信息,可以在此修改,在发送请求。在user.js中没有hearders,config也可以打印出来
  // console.log(22);
  // console.log(config);
  // console.log(11);
  const user = store.state.user
  console.log(user);
  if(user && user.token){
    config.headers.Authorization = `Bearer ${user.token}`
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 因为每次请求的token都需要手动添加,比较麻烦,所以添加请求拦截器,方便
举报

相关推荐

0 条评论