Skip to content
On this page

面经 H5 端 - Vant(上)

接口文档地址

项目创建目录初始化

vue-cli 建项目

创建项目

vue create hm-vant-h5
  • 选项
js
Vue CLI v5.0.4
? Please pick a preset: (Use arrow keys)
  Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
> Manually select features      选自定义
  • 手动选择功能-回车确定

image-20221224152939988

  • 选择 vue 的版本
jsx
3.x
> 2.x
  • 是否使用 history 模式

image-20201025150602129

  • 选择 css 预处理

image-20220629175133593

  • 选择配置文件的生成方式 (直接回车)

image-20201025151123023

  • 是否保存预设,下次直接使用? => 不保存,输入 N

image-20220613025710360

  • 等待安装,项目初始化完成

image-20220613025814865

  • 启动项目
npm run serve

调整初始化目录结构

强烈建议大家严格按照老师的步骤进行调整,为了符合企业规范

为了更好的实现后面的操作,我们把整体的目录结构做一些调整。

目标:

  1. 删除初始化的一些默认文件
  2. 修改没删除的文件
  3. 新增我们需要的目录结构

修改文件

  1. main.js 不需要修改

  2. router/index.js

删除默认的路由配置

js
import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const routes = [];

const router = new VueRouter({
  routes,
});

export default router;
  1. App.vue
html
<template>
  <div id="app">
    <router-view />
  </div>
</template>

删除文件

  • src/views/AboutView.vue

  • src/views/HomeView.vue

  • src/components/HelloWorld.vue

  • src/assets/logo.png

新增目录

  • src/api 目录
    • 存储接口模块 (发送 ajax 请求接口的模块)
  • src/utils 目录
    • 存储一些工具模块 (自己封装的方法)
  • src/assets 目录
    • 项目使用的素材

目录效果如下:

image-20220613033022857

vant 按需加载

vant-ui 组件库的引入

组件库:第三方封装好了很多很多的组件,整合到一起就是一个组件库。

https://vant-contrib.gitee.io/vant/v2/#/zh-CN/

组件库并不是唯一的

pc: element-ui iview ant-design

移动:vant-ui

按需加载

  • 安装 vant-ui
npm i vant@latest-v2
  • 安装一个插件
bash
npm i babel-plugin-import -D
  • babel.config.js中配置
js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"],
  plugins: [
    [
      "import",
      {
        libraryName: "vant",
        libraryDirectory: "es",
        style: true,
      },
      "vant",
    ],
  ],
};
  • 重启打包
  • 按需加载,在main.js
js
import { Button, Icon } from "vant";

Vue.use(Button);
Vue.use(Icon);
  • App.vue中进行测试
vue
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>

项目中的 vw 适配

官方说明:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/advanced-usage

js
npm i postcss-px-to-viewport@1.1.1 -D
  • 项目根目录, 新建 postcss 的配置文件postcss.config.js
js
// postcss.config.js
module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      viewportWidth: 375,
    },
  },
};

路由设计配置

但凡是单个页面,独立展示的,都是一级路由

路由设计:

  • 登录页 (一级) login
  • 注册页(一级) register
  • 文章详情页(一级) detail
  • 首页(一级) layout
    • 面经(二级)article
    • 收藏(二级)collect
    • 喜欢(二级)like
    • 我的(二级)my

一级路由

router/index.js配置一级路由, 一级 views

js
import Vue from "vue";
import VueRouter from "vue-router";
import Login from "@/views/Login";
import Register from "@/views/Register";
import Detail from "@/views/Detail";
import Layout from "@/views/Layout";
Vue.use(VueRouter);

const router = new VueRouter({
  routes: [
    { path: "/", component: Layout },
    { path: "/login", component: Login },
    { path: "/register", component: Register },
    { path: "/detail/:id", component: Detail },
  ],
});

export default router;

清理 App.vue

vue
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  async created() {},
};
</script>

tabbar 标签页

image-20220614061531984

https://vant-contrib.gitee.io/vant/v2/#/zh-CN/tabbar

main.js 引入组件

js
import { Button, Icon, Tabbar, TabbarItem } from "vant";
Vue.use(Tabbar);
Vue.use(TabbarItem);

layout.vue

vue
<template>
  <div class="layout-page">
    首页架子 - 内容区域
    <van-tabbar>
      <van-tabbar-item icon="notes-o">面经</van-tabbar-item>
      <van-tabbar-item icon="star-o">收藏</van-tabbar-item>
      <van-tabbar-item icon="like-o">喜欢</van-tabbar-item>
      <van-tabbar-item icon="user-o">我的</van-tabbar-item>
    </van-tabbar>
  </div>
</template>

二级路由

router/index.js配置二级路由

js
import Vue from "vue";
import VueRouter from "vue-router";
import Login from "@/views/login";
import Register from "@/views/register";
import Detail from "@/views/detail";
import Layout from "@/views/layout";

import Like from "@/views/like";
import Article from "@/views/article";
import Collect from "@/views/collect";
import User from "@/views/user";
Vue.use(VueRouter);

const router = new VueRouter({
  routes: [
    { path: "/login", component: Login },
    { path: "/register", component: Register },
    { path: "/article/:id", component: Detail },
    {
      path: "/",
      component: Layout,
      redirect: "/article",
      children: [
        { path: "article", component: Article },
        { path: "like", component: Like },
        { path: "collect", component: Collect },
        { path: "user", component: User },
      ],
    },
  ],
});

export default router;

layout.vue 配置路由出口, 配置 tabbar

jsx
<template>
  <div class="layout-page">
    <router-view></router-view>

    <van-tabbar route>
      <van-tabbar-item to="/article" icon="notes-o">
        面经
      </van-tabbar-item>
      <van-tabbar-item to="/collect" icon="star-o">
        收藏
      </van-tabbar-item>
      <van-tabbar-item to="/like" icon="like-o">
        喜欢
      </van-tabbar-item>
      <van-tabbar-item to="/user" icon="user-o">
        我的
      </van-tabbar-item>
    </van-tabbar>
  </div>
</template>

效果图:

image-20220701151639583

登录&注册功能

登录静态布局

image-20220614062935057

使用组件

  • van-nav-bar
  • van-form
  • van-field
  • van-button

vant-ui.js 注册

jsx
import Vue from "vue";
import { NavBar, Form, Field } from "vant";
Vue.use(NavBar);
Vue.use(Form);
Vue.use(Field);

login.vue 使用

jsx
<template>
  <div class="login-page">
    <!-- 导航栏部分 -->
    <van-nav-bar title="面经登录" />

    <!-- 一旦form表单提交了,就会触发submit,可以在submit事件中
         根据拿到的表单提交信息,发送axios请求
     -->
    <van-form @submit="onSubmit">
      <!-- 输入框组件 -->
      <!-- \w 字母数字_   \d 数字0-9 -->
      <van-field
        v-model="username"
        name="username"
        label="用户名"
        placeholder="用户名"
        :rules="[
          { required: true, message: '请填写用户名' },
          { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符' }
        ]"
      />
      <van-field
        v-model="password"
        type="password"
        name="password"
        label="密码"
        placeholder="密码"
        :rules="[
          { required: true, message: '请填写密码' },
          { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' }
        ]"
      />
      <div style="margin: 16px">
        <van-button block type="info" native-type="submit">提交</van-button>
      </div>
    </van-form>
  </div>
</template>

<script>
export default {
  name: 'login-page',
  data () {
    return {
      username: 'zhousg',
      password: '123456'
    }
  },
  methods: {
    onSubmit (values) {
      console.log('submit', values)
    }
  }
}
</script>

login.vue添加 router-link 标签(跳转到注册)

vue
<template>
  <div class="login-page">
    <van-nav-bar title="面经登录" />

    <van-form @submit="onSubmit"> ... </van-form>

    <router-link class="link" to="/register">注册账号</router-link>
  </div>
</template>

login.vue调整样式

jsx
<style lang="less" scoped>
.link {
  color: #069;
  font-size: 12px;
  padding-right: 20px;
  float: right;
}
</style>

注册静态布局

register.vue

jsx
<template>
  <div class="login-page">
    <van-nav-bar title="面经注册" />

    <van-form @submit="onSubmit">
      <van-field
        v-model="username"
        name="username"
        label="用户名"
        placeholder="用户名"
        :rules="[{ required: true, message: '请填写用户名' }]"
      />
      <van-field
        v-model="password"
        type="password"
        name="password"
        label="密码"
        placeholder="密码"
        :rules="[{ required: true, message: '请填写密码' }]"
      />
      <div style="margin: 16px">
        <van-button block type="primary" native-type="submit"
          >注册</van-button
        >
      </div>
    </van-form>
    <router-link class="link" to="/login">有账号,去登录</router-link>
  </div>
</template>

<script>
export default {
  name: 'register-page',
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    onSubmit (values) {
      console.log('submit', values)
    }
  }
}
</script>

<style lang="less" scoped>
.link {
  color: #069;
  font-size: 12px;
  padding-right: 20px;
  float: right;
}
</style>

request 模块 - axios 封装

接口文档地址:https://www.apifox.cn/apidoc/project-934563/api-19465917

我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址等)

一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用

  1. 安装 axios
npm i axios
  1. 新建 utils/request.js 封装 axios 模块

    利用 axios.create 创建一个自定义的 axios 来使用

    http://www.axios-js.com/zh-cn/docs/#axios-create-config

js
/* 封装axios用于发送请求 */
import axios from "axios";

// 创建一个新的axios实例
const request = axios.create({
  baseURL: "http://interview-api-t.itheima.net/",
  timeout: 5000,
});

// 添加请求拦截器
request.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
request.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response.data;
  },
  function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  }
);

export default request;
  1. 测试 (可以先注册后登录)
js
<script>
import request from '@/utils/request'
export default {
  async created () {

    const res = await request({
      url: '/user/login',
      body:  {
        username: 'shuaipeng',
        password: '123456'
      }
    });
    
    console.log(res)
  }
}
</script>

image-20220614054157121

toast 轻提示

将来无论注册成功,还是失败,都是需要给用户提示的

https://vant-contrib.gitee.io/vant/v2/#/zh-CN/toast

两种使用方式

  1. 无需注册,导入,调用
jsx
import { Toast } from "vant";
Toast("提示内容");
  1. 组件内,通过 this 直接调用 👍

注册

js
import { Toast } from "vant";
Vue.use(Toast);

调用

js
this.$toast("提示内容");

封装 api 接口 - 注册功能

新建 api/user.js 提供注册 Api 函数

jsx
import request from "@/utils/request";

// 注册接口
export const registerAPI = (data) => {
  return request.post("/user/register", data);
};

register.vue页面中调用测试

jsx
methods: {
  async onSubmit (values) {
    // 往后台发送注册请求了
    await registerAPI(values)
    this.$toast.success('注册成功')
    this.$router.push('/login')
  }
}

request.js响应拦截器,统一处理错误提示

jsx
import { Toast } from 'vant'

...

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data
}, function (error) {
  if (error.response) {
    // 有错误响应, 提示错误提示
    Toast(error.response.data.message)
  }
  // 对响应错误做点什么
  return Promise.reject(error)
})

封装 api 接口 - 登录功能

api/user.js 提供登录 Api 函数

jsx
// 登录接口
export const loginAPI = (data) => {
  return request.post("/h5/user/login", data);
};

login.vue 登录功能

jsx
import { loginAPI } from '@/api/user'
import { setToken } from '@/utils/storage'

methods: {
  async onSubmit (values) {
    const { data } = await loginAPI(values)
    setToken(data.token)
    this.$toast.success('登录成功')
    this.$router.push('/')
  }
}

local 模块 - 本地存储

新建 utils/storage.js

jsx
// 以前 token 令牌,如果存到了本地,每一次都写这么长,太麻烦
// localStorage.setItem(键, 值)
// localStorage.getItem(键)
// localStorage.removeItem(键)

const KEY = "my-token-vant-mobile";

// 获取
export const getToken = () => {
  return localStorage.getItem(KEY);
};

// 设置
export const setToken = (token) => {
  localStorage.setItem(KEY, token);
};

// 删除
export const delToken = () => {
  localStorage.removeItem(KEY);
};

导航守卫-页面访问拦截

这个 面经移动端 项目,只对 登录用户 开放,如果未登录,一律拦截到登录

  1. 如果访问的是 首页, 无 token, 拦走

  2. 如果访问的是 列表页,无 token, 拦走

  3. 如果访问的是 详情页,无 token, 拦走

    ....

分析:哪些页面,是不需要登录,就可以访问的! => 注册登录 (白名单 - 游客可以随意访问的)

核心逻辑:

  1. 判断用户有没有 token, 有 token, 直接放行 (有身份的人,想去哪就去哪~)
  2. 没有 token(游客),如果是白名单中的页面,直接放行
  3. 否则,无 token(游客),且在访问需要权限访问的页面,直接拦截到登录

src/router/index.js

jsx
// ... 省略其它代码

import { getToken } from "@/utils/storage";
// 全局前置守卫:
// 1. 所有的路由一旦被匹配到,在真正渲染解析之前,都会先经过全局前置守卫
// 2. 只有全局前置守卫放行,才能看到真正的页面

// 任何路由,被解析访问前,都会先执行这个回调
// 1. from 你从哪里来, 从哪来的路由信息对象
// 2. to   你往哪里去, 到哪去的路由信息对象
// 3. next() 是否放行,如果next()调用,就是放行 => 放你去想去的页面
//    next(路径) 拦截到某个路径页面

const whiteList = ["/login", "/register"]; // 白名单列表,记录无需权限访问的所有页面

router.beforeEach((to, from, next) => {
  const token = getToken();
  // 如果有token,直接放行
  if (token) {
    next();
    return;
  }

  // 没有token的人, 看看你要去哪
  // (1) 访问的是无需授权的页面(白名单),也是放行
  //  就是判断,访问的地址,是否在白名单数组中存在 includes
  if (whiteList.includes(to.path)) {
    next();
  } else {
    // (2) 否则拦截到登录
    Toast("没有登录, 请先登录!");
    next("/login");
  }
});

export default router;

Released under the MIT License.