失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > vuecli4+vant移动端响应式项目踩坑记录

vuecli4+vant移动端响应式项目踩坑记录

时间:2021-05-21 10:20:18

相关推荐

vuecli4+vant移动端响应式项目踩坑记录

文章目录

关键词参考链接一. 使用vue/cli4二. 使用vant三. 加入响应式布局1. rem适配插件2. PostCSS配置四. 图标库:封装svg图标组件1. 建立如下目录结构:2. components/SvgIcon.vue3. icon/index.js4. 配置vue.config.js5. svg图标使用五.axios+api封装目录结构(示例)http.js封装api/index.jsapi/user.jsapi注册到全局(main.js文件)api接口调用示例App.vue(断网代码示例)refresh.vue六. router全局守卫处理七. 跨域代理proxy -> 配置vue.config.js文件八. vscode中自定义配置prettier九. 查看隐藏的webpack配置:十. vantlist组件在ios手机中滑动无效

关键词

@vue/cli4, vant, rem, svg, axios

参考链接

vue移动端ui框架 - vant官网vue cli官网vue-cli4中配置移动端自适应postcss-pxtorem掘金-vue中Axios的封装和API接口的管理

一. 使用vue/cli4

全局安装@vue/cli最新版本

yarn add -g @vue/cli或者npm install @vue/cli -g

查看安装的vue/cli版本vue --version

创建vue项目vue create hello-world

创建项目时候让选择,默认or手动,一般选择手动,按照提示选择自己需要的即可。我选择了以下2个关键的。

CSS Pre-processors scss(node scss) eslint(prettier)

vue/cli有个小坑。如果删除了依赖,自己安装一遍,发现有警告⚠️:warning " > sass-loader@8.0.2" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".。应该是脚手架的坑,暂时不知怎么去改。

二. 使用vant

安装插件yarn add vant按需引入插件yarn add babel-plugin-import --dev(注:这个插件装到开发依赖)自动按需引入使用示例:

// template<van-button type="default">默认按钮</van-button>// scriptimport {Button } from "vant";components: {[Button.name]: Button}

三. 加入响应式布局

1. rem适配插件

postcss-pxtorem 是一款 postcss 插件,用于将单位转化为 rem 。(!安装到开发依赖 --dev)lib-flexible 用于动态改变根节点的font-size,设置 rem 基准值。(!安装到生产依赖 --save)【小坑】lib-flexible按照官网提供的在html引入js会报错,改为在main.js中引入依赖import "amfe-flexible/index.js";就ok了。

2. PostCSS配置

vue.config.js中配置

css: {loaderOptions: {postcss: {plugins: [require("autoprefixer")({// 配置使用 autoprefixeroverrideBrowserslist: ["last 15 versions"]}),require("postcss-pxtorem")({rootValue: 37.5, // 换算的基数// 忽略转换正则匹配项。插件会转化所有的样式的px。比如引入了三方UI,也会被转化。目前我使用 selectorBlackList字段,来过滤//如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px 。selectorBlackList: ["ig"],propList: ["*"]})]}}}

postcss.config.js中配置

module.exports = {plugins: {autoprefixer: {overrideBrowserslist: ['Android >= 4.0', 'iOS >= 8'],},'postcss-pxtorem': {rootValue: 37.5, // ⚠️这里是设计稿的1/10propList: ['*'],mediaQuery: true},},};

在配置 postcss-loader 时,应避免 ignore node_modules 目录,否则将导致 Vant 样式无法被编译

四. 图标库:封装svg图标组件

原因:svg放大后不失真,可以像css一样设置颜色,非常方便。使用步骤:

1. 建立如下目录结构:

iconindex.jssvgtest1.svg // (去阿里的iconfont随便下载一个来试验)test2.svgcomponentsSvgIcon.vue

2. components/SvgIcon.vue

<template><svg :class="svgClass" aria-hidden="true" v-on="$listeners"><use :xlink:href="iconName" /></svg></template><script>export default {name: "SvgIcon",props: {iconClass: {type: String,required: true},className: {type: String,default: ""}},computed: {iconName() {return `#icon-${this.iconClass}`;},svgClass() {if (this.className) {return "svg-icon " + this.className;} else {return "svg-icon";}}}};</script><style scoped>.svg-icon {width: 16px;height: 16px;vertical-align: -3px;fill: currentColor;overflow: hidden;}</style>

3. icon/index.js

import Vue from "vue";import SvgIcon from "@/components/SvgIcon"; // svg组件// register ponent("svg-icon", SvgIcon);const req = require.context("./svg", false, /\.svg$/);const requireAll = requireContext => requireContext.keys().map(requireContext);requireAll(req);

4. 配置vue.config.js

// 添加svg-sprite-loader,同时不要忽略了其他不作为图片的svg文件, // file-loader 用来处理除了icon/svg文件夹下其他地方的.svg文件chainWebpack: config => {const svgRule = config.module.rule("svg");// 清除已有的所有 loader。// 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。svgRule.uses.clear();svgRule.test(/\.svg$/).include.add(path.resolve(__dirname, "./src/icons/svg")).end().use("svg-sprite-loader").loader("svg-sprite-loader").options({symbolId: "icon-[name]"});const fileRule = config.module.rule("file");fileRule.uses.clear();fileRule.test(/\.svg$/).exclude.add(path.resolve(__dirname, "./src/icons/svg")).end().use("file-loader").loader("file-loader");}

5. svg图标使用

// class="color-red" 可以添加自定义的样式,可以覆盖默认的fill<svg-iconclass="color-red"icon-class="arrow_bottom_solid"></svg-icon>

五.axios+api封装

目录结构(示例)

requesthttp.jsapiindex.jsuser.js

http.js封装

import axios from "axios";import router from "../router";import store from "../store";/*** 提示函数* 禁止点击蒙层、显示一秒后关闭*/const tip = msg => {Toast({message: msg,duration: 1000,forbidClick: true});};/*** 跳转登录页* 携带当前页面路由,以期在登录页面完成登录后返回当前页面*/const toLogin = async () => {router.replace({path: "/login",query: {redirect: router.currentRoute.fullPath}});};/*** 请求失败后的错误统一处理* @param {Number} status 请求失败的状态码*/const errorHandle = status => {// 状态码判断switch (status) {// 401: 未登录状态,跳转登录页case 401:toLogin();break;// 403 token过期// 清除token并跳转登录页case 403:tip("登录过期,请重新登录");localStorage.removeItem("token");setTimeout(() => {toLogin();}, 1000);break;// 404请求不存在case 404:tip("请求的资源不存在");break;default:tip(`其他未知错误,状态码:${status}`);}};// 状态200时候, code码判断const errorCodeHandle = ({code, message }) => {switch (code) {case "000000": //系统交易成功break;case "999999": //系统异常tip(message);break;case "AUTH_x1": //用户未登陆mit("storeUser/clearUserInfo");toLogin();break;case "AUTH_x2": //用户无权限mit("storeUser/clearUserInfo");tip(message);break;case "LOGIN_x3": //用户已禁用mit("storeUser/clearUserInfo");tip(message);break;case "LOGIN_x4": //用户session失效mit("storeUser/clearUserInfo");toLogin();break;default:tip(message);break;}};// 创建axios实例var instance = axios.create({timeout: 5000 });// 设置post请求头instance.defaults.headers.post["Content-Type"] ="application/json;charset=UTF-8;";instance.defaults.baseURL = "api";// 请求拦截器instance.interceptors.request.use(config => {// 对config做一些处理// ...// 加载弹窗Toast.loading({message: "加载中...",forbidClick: true});return config;},error => Promise.error(error));// 响应拦截器instance.interceptors.response.use(// 请求成功res => {Toast.clear();if (!store.work) {mit("storeGlobal/changeNetwork", true);}if (res.status === 200 && res.data.code === "000000") {return Promise.resolve(res.data);} else {errorCodeHandle(res.data);return Promise.reject(res);}},// 请求失败error => {const {response } = error;if (response) {// 请求已发出,但是不在2xx的范围errorHandle(response.status, response.data.message);return Promise.reject(response);} else {// 处理断网的情况// eg:请求超时或断网时,更新state的network状态// network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏// 关于断网组件中的刷新重新获取数据,会在断网组件(refresh.vue)中说明if (!window.navigator.onLine) {mit("storeGlobal/changeNetwork", false);} else {return Promise.reject(error);}}});export default instance;

api/index.js

/*** api接口的统一出口*/import user from "@/request/api/user";// 导出接口export default {user};

api/user.js

/*** user模块接口列表*/import axios from "@/request/http"; // 导入http中创建的axios实例const user = {login(params) {return axios.post("/login", params);}};export default user;

api注册到全局(main.js文件)

import api from '@/request/api';Vue.prototype.$api = api;

api接口调用示例

// Login.vuemethods: {async onSubmit() {let params = {loginName: '小美',password: '123'};const res = await this.$api.login(params);console.log("登录信息:", res)}}

App.vue(断网代码示例)

使用一个全局的store状态存储网络状态

<template><div id="app"><div v-if="!network" class="offline">哎呀,网络开小差啦。<van-icon name="replay" @click.native="onRefresh" /></div><router-view /></div></template><script>import {mapState } from "vuex";import {Icon } from "vant";export default {components: {[Icon.name]: Icon},computed: {...mapState("storeGlobal", ["network"])},methods: {onRefresh() {this.$router.replace("/refresh");}}};</script><style lang="scss">#app {font-family: STHeitiSC-Medium, STHeitiSC, Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;color: #2c3e50;background-color: #f5f5f5;height: 100vh;.offline {text-align: center;padding: 10px;background-color: #ffeeaa;font-size: 14px;display: flex;align-items: center;justify-content: center;}}</style>

refresh.vue

<template><div></div></template><script>/* 从app.vue来,这里简单介绍一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,* 那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。* 当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。* 因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。*/export default {beforeRouteEnter(to, from, next) {next(vm => {vm.$router.replace(from.fullPath);});}};</script>

六. router全局守卫处理

// router/index.jsconst routes = [{path: "/login",name: "Login",component: Login,meta: {title: "登录"}},]router.beforeEach((to, from, next) => {// 添加title, 无需每个页面设置if (to.meta && to.meta.title) {document.title = to.meta.title;}// 添加路由来源,无需每个页面添加路由守卫判断来自哪个页面to.params.last = from;next();});

七. 跨域代理proxy -> 配置vue.config.js文件

从vue/cli3开始项目就看不到webpcak.config.js之类的配置文件了。需要添加前端代理需要自己在根目录下添加vue.config.js进行配置。

下面⬇️展示一个配置比较齐全的文件。

const path = require("path");module.exports = {/* 部署生产环境和开发环境下的URL:可对当前环境进行区分,baseUrl 从 Vue CLI 3.3 起已弃用,要使用publicPath *//* baseUrl: process.env.NODE_ENV === 'production' ? './' : '/' */publicPath: process.env.NODE_ENV === "production" ? "/public/" : "./",/* 输出文件目录:在npm run build时,生成文件的目录名称 */outputDir: "dist",/* 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录 */assetsDir: "assets",/* 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度 */productionSourceMap: false,/* 默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存,你可以通过将这个选项设为 false 来关闭文件名哈希。(false的时候就是让原来的文件名不改变) */filenameHashing: false,/* 代码保存时进行eslint检测 */lintOnSave: true,/* webpack-dev-server 相关配置 */devServer: {/* 自动打开浏览器 */open: true,/* 设置为0.0.0.0则所有的地址均能访问 */host: "0.0.0.0",port: 8088,https: false,hotOnly: false,/* 使用代理 */proxy: {"/sunrise-gateway": {/* 目标代理服务器地址 */target: "/",/* 允许跨域 */changeOrigin: true}}},css: {loaderOptions: {postcss: {plugins: [require("autoprefixer")({// 配置使用 autoprefixeroverrideBrowserslist: ["last 15 versions"]}),require("postcss-pxtorem")({rootValue: 37.5, // 换算的基数// 忽略转换正则匹配项。插件会转化所有的样式的px。比如引入了三方UI,也会被转化。目前我使用 selectorBlackList字段,来过滤//如果个别地方不想转化px。可以简单的使用大写的 PX 或 Px 。selectorBlackList: ["ig"],propList: ["*"]})]}}},chainWebpack: config => {const svgRule = config.module.rule("svg");// 清除已有的所有 loader。// 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。svgRule.uses.clear();svgRule.test(/\.svg$/).include.add(path.resolve(__dirname, "./src/icons/svg")).end().use("svg-sprite-loader").loader("svg-sprite-loader").options({symbolId: "icon-[name]"});const fileRule = config.module.rule("file");fileRule.uses.clear();fileRule.test(/\.svg$/).exclude.add(path.resolve(__dirname, "./src/icons/svg")).end().use("file-loader").loader("file-loader");}};

八. vscode中自定义配置prettier

vscode安装插件:Prettier - Code formatter问题:插件格式化的文件和vuecli要求的prettimer需要的不一致。所以需要自定义配置成vuecli要求的效果。解决:代码(code) -> 首选项(preference) -> 设置(settings) -> extensions -> premitter具体配置可以参考premitter配置文件官方网站中文的找到一篇基本配置+解释的参考文章Prettier格式化配置

{// 使能每一种语言默认格式化规则"[html]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[css]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[less]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[javascript]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},/* prettier的配置 */"prettier.printWidth": 100, // 超过最大值换行"prettier.tabWidth": 4, // 缩进字节数"prettier.useTabs": false, // 缩进不使用tab,使用空格"prettier.semi": true, // 句尾添加分号"prettier.singleQuote": true, // 使用单引号代替双引号"prettier.proseWrap": "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行"prettier.arrowParens": "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号"prettier.bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }""prettier.disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化单独设置"prettier.endOfLine": "auto", // 结尾是 \n \r \n\r auto"prettier.eslintIntegration": false, //不让prettier使用eslint的代码格式进行校验"prettier.htmlWhitespaceSensitivity": "ignore","prettier.ignorePath": ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中"prettier.jsxBracketSameLine": false, // 在jsx中把'>' 是否单独放一行"prettier.jsxSingleQuote": false, // 在jsx中使用单引号代替双引号"prettier.parser": "babylon", // 格式化的解析器,默认是babylon"prettier.requireConfig": false, // Require a 'prettierconfig' to format prettier"prettier.stylelintIntegration": false, //不让prettier使用stylelint的代码格式进行校验"prettier.trailingComma": "es5", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)"prettier.tslintIntegration": false // 不让prettier使用tslint的代码格式进行校验}

九. 查看隐藏的webpack配置:

vue inspect 执行后,控制台会显示你的webpack所有的配置vue inspect --rules 显示所有的rule配置规则vue inspect --rule svg (我们在上面配置了svg)

十. vantlist组件在ios手机中滑动无效

发现设置了 高度为100vh的#app标签,没有设置overflow,其他浏览器都默认可以滑动。ios上的wkwebview中不能滑动,设置上overflow: auto就好啦

如果觉得《vuecli4+vant移动端响应式项目踩坑记录》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。