vue3+vant创建移动端项目,实战项目常见采坑记录
阅读原文时间:2023年07月13日阅读:1

前言:

  产品背景介绍

  我所做的这个项目,刚开始是没有移动端需求的,等PC端做完了上线使用了几个月后,突然有一天产品经理找到我说是要做一个在PC端添加一个快速注册入口,用手机微信扫二位码进入移动端注册页面,用户注册。

所以本次需求就是在PC端添加一个tool-tip气泡型弹出二维码,再开发一个移动端注册页面。

  起初我是在PC项目中引入vant新加了一个模块来存放移动注册页面和注册成功页面的,然后想使用postcss-px-to-viewport的exclude和include属性配置来区分PC和移动页面,避免样式干扰。

然而,是我天真了,看网上各种postcss-px-to-viewport的exclude和include的配置,更换各个版本以及相似的更新版本,都不能完美做到兼容移动端和PC端,我就放弃了移动pc放在一个项目中了,最终只能单独的把移动页面单独摘出来成立一个单独项目跑,坑爹啊。

  1. ### 表单密码可见切换以及不让输入汉字空格

代码实现:

<van-field

              :required="true"

              v-model="registerForm.password"

              :type="switchPassType ? 'text' : 'password'"

              name="password"

              label="登录密码"

              placeholder="请输入登录密码"

              :right-icon="switchPassType ? 'eye' : 'closed-eye'"

              @click-right-icon="switchPassType = !switchPassType"

              :rules="rules.password"

              :update:model-value="registerForm.password=registerForm.password.replace(/[\u4e00-\u9fa5/\s+/]/ig,'')"

            />

备注:密码这块虽然有些网上搜到的让用vant 自带的digit属性让只能输入数字,但这个不符合产品需求,密码应为数字、字母、特殊符号都可输入。

使用van-field自带的update:model-value方法进行汉字、空格校验,亲测有效。

  2. 移动端页面出现X轴滚动条问题

1) 在index.html文件中添加

2) 在公共样式中修改html、body设置的样式

html {

    overflow-y: scroll;

}

:root {

    overflow-y: auto;

    overflow-x: hidden;

}

:root body {

    position: absolute;

}

body {

    width: 100%;

    margin: 0;

    padding: 0;

    overflow: hidden;

}

  3.使用postcss-px-to-viewport做适配出现的问题

使用教程我就不赘述了,网上一大片,说点有用的

1)兼容vant的375设置

在你的vue.config.js文件中添加如下代码:

css: {

    loaderOptions: {

      postcss: {

        postcssOptions: (loaderContext) => {

          return {

            plugins: [

              ["autoprefixer"],

              // vant px转vw。参坑:单独写在postcss.config.js中无法解析vant内部样式

              {

                "postcss-px-to-viewport": {

                  unitToConvert: "px",

                  viewportWidth: loaderContext.resourcePath.includes("vant") ? 375 : 750,

                  // viewportWidth: 750,  // 视窗的宽度,对应的是我们设计稿的宽度,一般是750

                  // viewportHeight: 1334, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置

                  unitPrecision: 6, // 指定`px`转换为视窗单位值的小数位数

                  propList: ["*"],

                  viewportUnit: "vw", //指定需要转换成的视窗单位,建议使用vw

                  fontViewportUnit: "vw",

                  selectorBlackList: [], // 如['.ignore'], 可以指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名

                  minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值

                  mediaQuery: true, // 允许在媒体查询中转换`px`

                  exclude: [],

                  landscape: false

                },

              },

            ],

          };

        },

      },

    },

  }

代码目录:

备注:其中的exclude属性不知道树我本人写的有问题还是怎么的,想用它处理views目录下的特定目录文件总是不生效,include就更别说了,各种版本、各种写法都换过了没啥nuan用。

  4.检测是否是移动端打开跳转不同路由

1)路由处理

export const ISMOBILE = function () {

    let flag = navigator.userAgent.match(

        /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i

    );

    return flag;

};

  5. DatePicker日期选择器默认选中当前日期

在这块有2个坑:

1)它的年份默认最小2013,不能像elementui的日期选择器一样自由选择,所以,使用时你可以使用min-date属性设置最小年月日。

2)默认选中当前年月日,

currentDate使用计算属性获取,

备注:月日不足2位要用0补齐,不然获取到的当前年月日不对

/**获取当前日期回填 */

const currentDate = computed(() => {

  const nowDate = myDate.toLocaleDateString().split("/");

  if (nowDate[1].length < 2) {

    nowDate[1] = "0" + nowDate[1];

  }

  if (nowDate[2].length < 2) {

    nowDate[1] = "0" + nowDate[2];

  }

  return nowDate;

});

  6. 表单校验添加

1)一定要给van-form中添加ref属性,在van-field中使用name作为校验标识,使用rules属性添加校验规则。

备注:想要对一个字段进行不同校验以及不同提示,只能使用多条校验规则。此处不能像使用elementUi中的callback返回提示语,只能使用message属性写死,操蛋啊

  7.文件上传预览删除

直接上代码,很明了:

// 上传图片、上传文件校验大小和格式

const beforeRead = (file) => {

  const correctFormat = ["jpg", "jpeg", "png", "bmp"];

  const isArray = Object.prototype.toString.call(file) === "[object Array]";

  const isLimit50M = 1024 * 1024 * 6; // 是否大于50M

  if (isArray) {

    const sizes = file.map((item) => item.size);

    if (sizes.some((item) => item > isLimit50M)) {

      showNotify({ type: "warning", message: "大小不能超过6M" });

      return false;

    }

    const types = file.map(

      ({ name }) => name.slice(name.lastIndexOf(".") + 1) // 后缀

    );

    if (!types.every((item) => correctFormat.includes(item))) {

      showNotify({ type: "warning", message: "请上传jpg/jpeg/png/bmp" });

      return false;

    }

  } else {

    if (file.size > isLimit50M) {

      showNotify({ type: "warning", message: "大小不能超过6M" });

      return false;

    }

    const type = file.name.slice(file.name.lastIndexOf(".") + 1);

    if (!correctFormat.includes(type)) {

      showNotify({ type: "warning", message: "请上传jpg/jpeg/png/bmp" });

      return false;

    }

  }

  return true;

};

// 文件上传

const fileUpload = (data, key, id, file) => {

  fileUploadApi(data)

    .then((res) => {

      if (res.status === 1) {

        res.data.key = new Date().getTime();

        res.data.url = res.data.attachPreviewUrl;

        res.data.percent = 100;

        res.data.status = "success";

        res.data.name = res.data.attachSrcName;

        res.data.uid = id;

        registerForm[key] = [];

        registerForm[key].push(res.data);

        file.status = "success";

        showNotify({ type: "success", message: "上传成功" });

        return true;

      } else {

        file.status = "failed";

        showNotify({ type: "danger", message: "上传失败,请重新上传" });

        return false;

      }

    })

    .catch((err) => {

      showNotify({ type: "danger", message: err.message });

      return false;

    });

};

/**文件上傳动作 */

const myBeforeRead = (file, field) => {

  if (beforeRead(file)) {

    file.status = "uploading";

    file.message = "上传中…";

    uploadData.file = file;

    uploadData.field = field;

    const form = new FormData();

    const keys = Object.keys(uploadData);

    keys.forEach((key) => {

      form.append(key, uploadData[key]);

    });

    // 后台接口

    if (fileUpload(form, uploadData.field, file.lastModified, file)) {

      return true;

    } else {

      return false;

    }

  } else {

    return false;

  }

};

/**文件删除动作 */

const delImg = (file, field) => {

  registerForm[field] = [];

  proxy.$refs.registerFormRef.validate(field);

};