vue+vant搭建移动端框架

主要内容

1.vant/rem移动端适配的解决方案
2. vue/cli二次配置及优化处理
3.基于注册动态模式实现loading
4.掌握vuex永久化存储

1 创建一个vue项目

1.1安装vue-cli:

npm install -g @vue/cli

1.2初始化项目

vue create vue-vant

(1)选择手动配置
空格切换选中状态
插件选择这边选择了(Babel、Router、Vuex、Css预处理器、Linter / Formatter 格式检查)
在这里插入图片描述Babel:支持es6语法转换
TypeScript:微软提供的js超集
Progressive Web App (PWA) Support:渐进式的网页应用程序。
Router:路由
Vuex:Vuex是Vue.js应用程序的状态管理模式+库
CSS Pre-processors:Sass/Less预处理器
Linter / Formatter:代码风格检查
Unit Testing:支持单元测试
E2E Testing:支持E2E测试
(2)
在这里插入图片描述路由模式选择: 是否使用history模式的路由(yes)
选择一个css预处理器(Less)
选择一个eslint配置,这边选择Airbnb
选择在什么时候进行eslint校验, 选择Lint on save保存时检查
选择将这些配置写入到什么地方( In package.json)
是否保存这份预设配置(n)
到此等待依赖完成

1.3启动项目

cd vue-vant
npm run serve

2.通过npm安装

npm I vant -S

参考vant官网快速上手教程: https://youzan.github.io/vant/#/zh-CN/quickstart

3.引入组件

自动按需引入(推荐)
3.1安装babel-plugin-import插件
babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式
#安装插件

npm i babel-plugin-import -D

3.2添加配置
在babel.config.js 中添加配置

{
  "plugins": [
    ["import", {
      "libraryName": "vant",
      "libraryDirectory": "es",
      "style": true
    }]
  ]
}

3.3引入vant组件
如在全局引入懒加载组件
src/main.js

//引入组件
import {Lazyload} from 'vant'
Vue.use(Lazyload)

引入组件后发现报错:是因为项目开启了eslint检查
解决办法: 在项目根目录新建vue.config.js文件,去除在保存的时候检查代码的功能, 配置完后记得重新npm run serve启动。
vue.config.js

 module.exports = {
     lintOnSave: false //eslint-loader 是否在保存的时候检查
 }

vue.config.js是vue-cli3之后新增的一个功能,再这个版本里面如果要配置webpack相关的属性,就需要自己在项目根目录新建vue.config.js这个文件,然后在该文件里面去写入你需要的配置。
后面再对vue.config.js进行更详细配置。

4.rem适配

Vant 中的样式默认使用px作为单位,如果需要使用rem单位,推荐使用以下两个工具:

postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 rem
lib-flexible 用于设置 rem 基准值

4.1安装

npm install postcss-pxtorem lib-flexible --save-dev

4.2postcss配置
在项目目录下新建文件postcss.config.js
postcss.config.js

//postcss的配置
module.exports = {
  plugins: {
    autoprefixer: {
      browsers: ['Android >= 4.0', 'iOS >= 8'],
    },
    'postcss-pxtorem': {
      rootValue: 37.5,
      propList: ['*'],
    },
  },
};

4.3引入 lib-flexible
main.js

//引入lib-flexible 用于设置rem基准值
import "lib-flexible/flexible"

rem详解

1rem等于html根元素设定的font-size的px值,vantui设置rootValue: 37.5,则1rem 等于 37.5px 等于 设备的宽度/10
切换不同的机型, 根元素有不同的font-size, 当写css px样式时, 会被程序换算成rem达到适配 使用vant组件,
需要按照rootValue:37.5来写样式

举个例子: 一张750px * 1334px图片, 在iphone6上铺满屏幕, 其他机型适配
当rootValue:75, 样式width:750px;height:1334px;图片会撑满iphone6屏幕,切换其他机型图片同样撑满屏幕
当rootValue:37.5, 样式width:375px;height:667px;图片会撑满iphone6屏幕

配置完如下图所示说明配置成功

<html data-dpr="1" style="font-size: 41.4px;"></html>

在这里插入图片描述

5.Tabbar标签栏的使用

5.1初始化项目
App.vue

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

Home.vue

<template>
  <div class="home">
    
  </div>
</template>

<script>

export default {
  name: 'Home',
  
};
</script>

5.2引入
App.vue

<script>
import { Tabbar, TabbarItem } from 'vant';
export default {
  components: {
    [Tabbar.name]: Tabbar,
    [TabbarItem.name]: TabbarItem,
  },
}
</script>

5.3路由模式
标签栏支持路由模式,用于搭配vue-router使用。路由模式下会匹配页面路径和标签的to属性,并自动选中对应的标签
App.vue

<router-view/>

<van-tabbar route>
  <van-tabbar-item replace to="/" icon="home-o">首页</van-tabbar-item>
  <van-tabbar-item replace to="/about" icon="orders-o">介绍</van-tabbar-item>
</van-tabbar>

实现效果如下图
在这里插入图片描述

6.Swipe轮播的使用

6.1引入
Home.vue

<script>
import { Swipe, SwipeItem} from "vant";
export default {
  name: 'Home',
  components: {
    [Swipe.name]: Swipe,
    [SwipeItem.name]: SwipeItem
  },
};
</script>

6.2图片懒加载
当 Swipe 中含有图片时,可以配合 Lazyload 组件实现图片懒加载。
Home.vue

<van-swipe :autoplay="3000">
  <van-swipe-item v-for="(image, index) in images" :key="index">
    <img v-lazy="image" />
  </van-swipe-item>
</van-swipe>
import Vue from 'vue';
import { Lazyload } from 'vant';

Vue.use(Lazyload);

export default {
  data() {
    return {
      images: [
        'https://img.yzcdn.cn/vant/apple-1.jpg',
        'https://img.yzcdn.cn/vant/apple-2.jpg',
      ],
    };
  },
};

6.3 设置图片样式
Home.vue

<template>
  <div class="home">
    <van-swipe :autoplay="3000">
      <van-swipe-item v-for="(image, index) in images" :key="index">
        <img v-lazy="image" />
      </van-swipe-item>
  </van-swipe>
  </div>
</template>

<script>
import { Swipe, SwipeItem} from "vant";
export default {
  name: 'Home',
  data() {
    return {
      images: [
        'https://img.yzcdn.cn/vant/apple-1.jpg',
        'https://img.yzcdn.cn/vant/apple-2.jpg',
      ],
    };
  },
  components: {
    [Swipe.name]: Swipe,
    [SwipeItem.name]: SwipeItem
  },
};
</script>

<style scoped>
  img{
    width: 100%;
  }
</style>

最终效果如下图:
在这里插入图片描述

7.配置多环境变量

7.1配置介绍

以 VUE_APP_ 开头的变量,在代码中可以通过 process.env.VUE_APP_ 访问。
比如,VUE_APP_BASE_API = ‘development’ 通过process.env.VUE_APP_BASE_API访问。   
除了 VUE_APP_* 变量之外,在你的应用代码中始终可用的还有两个特殊的变量NODE_ENV 和BASE_URL。

7.2在项目根目录中新建.env, .env.development 和 .env.production, 在文件中定义变量
.env.development 本地开发环境配置

NODE_ENV = "development";
BASE_URL = "/";
VUE_APP_BASE_API ="/dev-api";

.env.production 正式环境配置

NODE_ENV = "production";
BASE_URL = "/";
VUE_APP_BASE_API ="/prod-api";

.env 公共环境配置

NODE_ENV = "development";
BASE_URL = "/";
VUE_APP_BASE_API ="/dev-api";
VUE_APP_AA="aa"

7.3 vue.config.js配置
vue.config.js的具体配置参数可以参照vue-cli文档地址:https://cli.vuejs.org/zh/config/#crossorigin
vue.config.js

// module.exports = {
//     lintOnSave: false //eslint-loader 是否在保存的时候检查
// }

console.log(process.env.NODE_ENV);
const port  = process.env.port || 9999;

module.exports = {
    // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码 
    // (在生产构建时禁用 eslint-loader) 应该是!==,这里设置为===是为了不进行检查
    lintOnSave: process.env.NODE_ENV === 'production',
    devServer:{
        port,
        open:true,
        // 前端应用和后端 API 服务器没有运行在同一个主机上,
        // 你需要在开发环境下将 API 请求代理到 API 服务器
        proxy:{
            [process.env.VUE_APP_BASE_API]:{
                target:"http://localhost:8000/mock",// 接口的域名
                changeOrigin:true,// 开启代理,在本地创建一个虚拟服务端
                pathRewrite:{
                    [process.env.VUE_APP_BASE_API]:""
                }
            }
        }
    }
}

8.配置alias别名

vue.config.js

const path = require('path');
function resolve(dir){
    return path.join(__dirname,dir);
}
module.exports = {
    
    configureWebpack:{
        resolve:{
            alias:{
                "@":resolve("src"),
                "components":resolve("src/components"),
                "utils":resolve("src/utils")
            }
        }
    }
}

9.使用vuex实现按钮功能

9.1添加按钮
Home.vue

<template>
  <div class="home">
    <van-button type="primary" @click="addOne">按钮</van-button>
  </div>
</template>
<script>
import { Button } from "vant";
export default {
  name: 'Home',
  components: {
    [Button.name]: Button
  },
  methods: {
  	//添加按钮触发事件
    addOne(){
      console.log("点击了按钮")
    }
  }
};
</script>

9.2使用Vuex 实现点击按钮两秒钟后数字加1
store/index.js

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

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  },
});

main.js引入

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

使用
store/index.js

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

Vue.use(Vuex);

export default new Vuex.Store({
  //初始状态, 存储基本数据
  state: {
    number: 1
  },
  //更改store中状态的方法
  mutations: {
    oneAsync(state, payload){
      state.number = state.number + payload
    }
  },
  //用于提交mutation
  actions: {
    oneAsync({commit}, payload){
      return new Promise((resolve, reject) => {
        window.setTimeout(() => {
          resolve()
          commit("oneAsync", payload)
        }, 2000)
      })
    }
  },
  //允许将单一的store拆分为多个store
  modules: {
  },
});

Home.vue

<template>
  <div class="home">
    <h1>当前数据是:{{$store.state.number}}</h1>
    <van-button type="primary" @click="addOne" :loading="loading">按钮</van-button>
  </div>
</template>
<script>
import { mapActions } from 'vuex';
export default {
  name: 'Home',
  components: {
    [Button.name]: Button
  },
  methods: {
    //mapActions: 创建组件方法分发actions
    // 将 `this.oneAsync()` 映射为 `this.$store.dispatch('oneAsync')`
    // store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise
    ...mapActions(["oneAsync"]),
    addOne(){
      this.oneAsync(1)//加1
    }
  }
};
</script>

实现效果如下,点击按钮两秒后下图当前数据将变为2:
在这里插入图片描述
vuex的理解:
vuex文档: https://vuex.vuejs.org/zh/guide/

vuex是一个专门为vue.js开发的状态管理模式,每一个vuex应用核心就是store(仓库)。store基本上就是一个容器,它包含着你的应用中大部分的state(状态)
vuex的状态存储是响应式的,当 vue组件中store中读取状态时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
改变store中的状态的唯一途径就是显示 commit(提交)mutation,这样使得我们可以方便地跟踪每一个状态的变化。

主要有以下几个模块:
State: 定义了应用状态的数据结构,可以在这里设置默认的初始状态
Getter: 允许组件从Stroe中获取数据,mapGetters辅助函数仅仅是将store中的 getter映射到计算属性。
Mutation: 唯一更改store中状态的方法,且必须是同步函数。
Action: 用于提交muatation, 而不是直接变更状态,可以包含任意异步操作。
Module: 允许将单一的store拆分为多个 sotre且同时保存在单一的状态树中

10自定义 vuex-plugins-loading

插件功能: https://vuex.vuejs.org/zh/guide/plugins.html
subscribeAction: https://vuex.vuejs.org/zh/api/#subscribeaction

实现效果: 页面在数据加载完成前展示loading

实现思路:vuex中注册一个管理loading的module,通过绑定异步的action,将每个action的loading存在vuex中,这样我在每个页面只需要在vuex的store中拿相对应的action loading就能达到此目的

在src目录下新建utils目录,在utils目录下再新建vuex-loading.js
utils/vuex-loading.js

const NAMESPACED = "myLoading";
const createLoadingPlugin = ({namespaced = NAMESPACED}={})=>{
    return store =>{
        if(store.state[namespaced]){
            throw new Error("createLoadingPlugin is exited");
        }
        store.registerModule(namespaced, {
            namespaced:true,
            state:{
                effect:{}
            },
            mutations:{
                show(state,payload){
                    state.effect = {
                       ...state.effect,
                       [payload]:true   
                    }
                },
                hide(state,payload){
                    state.effect = {
                        ...state.effect,
                        [payload]:false   
                     }    
                }
            }
          })
        // 核心代码
        // 也可以指定订阅处理函数的被调用时机应该在一个 action 分发之前还是之后 (默认行为是之前)
        // 订阅 store 的 action, 
        // handler 会在每个 action 分发的时候调用并接收 action 描述和当前的 store 的 state 这两个参数
        store.subscribeAction({
            // 调用action之后loading一直是true
            before: (action, state) => {
              console.log(`before action ${action.type}`)//before action oneAsync
              store.commit(namespaced+"/show",action.type)
            },
            after: (action, state) => {
              console.log(`after action ${action.type}`)//after action oneAsync
              store.commit(namespaced+"/hide",action.type)
            }
          })
    }
}
export default createLoadingPlugin;

引入并使用插件
store/index.js

import Vue from 'vue';
import Vuex from 'vuex';
//引入插件
import createLoadingPlugin from '../utils/vuex-loading';

Vue.use(Vuex);

export default new Vuex.Store({
  //使用插件
  plugins: [createLoadingPlugin()],
});

Home.vue

<template>
  <div class="home">
    <!-- 在点击前后增加loading -->
    <h1>当前数据是:{{$store.state.number}}</h1>
    <van-button type="primary" @click="addOne" :loading="loading">按钮</van-button>
    {{$store.state.myLoading.effect.oneAsync}}
  </div>
</template>
<script>
import { Swipe, SwipeItem } from 'vant';
import { Button } from 'vant';
import { mapActions, mapState } from 'vuex';
export default {
  name: 'Home',
  computed: {
    //拿到action中的loading
    ...mapState({loading:state=>state.myLoading.effect["oneAsync"]})
  },
  components: {
    [Swipe.name]: Swipe,
    [SwipeItem.name]: SwipeItem,
    [Button.name]: Button
  },
  methods: {
    //mapActions: 创建组件方法分发actions
    // 将 `this.oneAsync()` 映射为 `this.$store.dispatch('oneAsync')`
    // store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise
    ...mapActions(["oneAsync"]),
    addOne(){
      this.oneAsync(1)
    }
  }
};
</script>

11.vuex永久化存储

vuex 是 vue的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换。

store/index.js

import Vue from 'vue';
import Vuex from 'vuex';
//引入插件
import createLoadingPlugin from '../utils/vuex-loading';

Vue.use(Vuex);

//vuex永久化存储
function vuexlocal(){
  return store =>{
    //看下本地是否保存数据
    //监视  subscribe
    // JSON.parse 将字符串转化为对象
    // JSON.stringify 将 JavaScript 值转换为 JSON 字符串
    let local = JSON.parse(localStorage.getItem("myVuex")) ||store.state;
    store.replaceState(local);
    store.subscribe((mutation,state)=>{
      let newState = JSON.parse(JSON.stringify(state));
      localStorage.setItem("myVuex",JSON.stringify(newState))
    })
  }
}
  • 26
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值