使用uniapp开发支付宝小程序(h5转支付宝小程序)

背景:嵌入app的H5页面需要变成支付宝小程序版本。

使用框架:"vue": "^2.6.11"
使用依赖:

"vue-router": "^3.2.0", 
"vuex": "^3.4.0",
"axios": "^0.19.2",
"crypto-js": "^4.0.0", // 加密解密
"exif-js": "^2.3.0", // 获取图片元信息
"vant": "^2.10.0",
"vue-cli-plugin-svg-sprite": "^1.0.0", // svg引入库

下方详解这些框架和组件在uniapp的可用或替换方案。
目录总结:
(1)vue-router => uni-simple-router
(2)axios => luch-request
(3)exif-js => uni.getImageInfo(OBJECT)
(4) vant => vant-ailapp(其实我是没有用,因为我需要的常用组件基本都报错了,如果你用的vant组件不多,可以试试。还是换成了官方的基础组件和官方的扩展组件(uni-ui)
(5)vue-cli-plugin-svg-sprite => 作为图片引入:background-image/(还是老办法兼容性好啊)


uniapp版本:

// package.json
{
  ...
  "dependencies": {
    "@dcloudio/uni-app-plus": "^2.0.0-28920200907001",
    "@dcloudio/uni-h5": "^2.0.0-28920200907001",
    "@dcloudio/uni-helper-json": "^1.0.6",
    "@dcloudio/uni-mp-alipay": "^2.0.0-28920200907001",
    "@dcloudio/uni-mp-baidu": "^2.0.0-28920200907001",
    "@dcloudio/uni-mp-qq": "^2.0.0-28920200907001",
    "@dcloudio/uni-mp-toutiao": "^2.0.0-28920200907001",
    "@dcloudio/uni-mp-weixin": "^2.0.0-28920200907001",
    "@dcloudio/uni-quickapp-native": "^2.0.0-28620200814005",
    "@dcloudio/uni-quickapp-webview": "^2.0.0-28920200907001",
    "@dcloudio/uni-stat": "^2.0.0-28920200907001",
    "copy-webpack-plugin": "5.0.0",
    "core-js": "^3.6.4",
    "crypto-js": "^4.0.0",
    "exif-js": "^2.3.0",
    "flyio": "^0.6.2",
    "luch-request": "^3.0.4",
    "mp-storage": "^1.0.3",
    "regenerator-runtime": "^0.12.1",
    "sass": "^1.26.10",
    "sass-loader": "^10.0.2",
    "uni-simple-router": "^1.5.5",
    "vue": "^2.6.12",
    "vuex": "^3.5.1"
  },
  "devDependencies": {
    "@dcloudio/types": "^2.0.6",
    "@dcloudio/uni-automator": "^2.0.0-28920200907001",
    "@dcloudio/uni-cli-shared": "^2.0.0-28920200907001",
    "@dcloudio/uni-migration": "^2.0.0-28920200907001",
    "@dcloudio/uni-template-compiler": "^2.0.0-28920200907001",
    "@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.0-28920200907001",
    "@dcloudio/vue-cli-plugin-uni": "^2.0.0-28920200907001",
    "@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.0-28920200907001",
    "@dcloudio/webpack-uni-mp-loader": "^2.0.0-28920200907001",
    "@dcloudio/webpack-uni-pages-loader": "^2.0.0-28920200907001",
    "@vue/cli-plugin-babel": "~4.3.0",
    "@vue/cli-service": "~4.3.0",
    "babel-plugin-import": "^1.11.0",
    "cross-env": "^7.0.2",
    "jest": "^25.4.0",
    "mini-types": "^0.1.4",
    "miniprogram-api-typings": "^3.0.2",
    "postcss-comment": "^2.0.0",
    "vue-template-compiler": "^2.6.12"
  },
  "browserslist": [
    "Android >= 4",
    "ios >= 8"
  ],
  "uni-app": {
    "scripts": {}
  }
}


一、选择uni-app的原因

九月初看了很多小程序框架测评文章,发现uniapp是对vue语法支持度最高的跨端框架,也是支持端最多的,所以对于那些从vue转手小程序的,使用uniapp会比较顺手,重构相对方便。
使用react的开发者可以尝试用taro,其他同事测试下来,react H5重构为taro 支付宝小程序基本没有问题。

二、创建项目

这个在uniapp官网可以得到详细的指导:快速上手 - uni-app官网

官方给出了两种创建项目的方式,一种是通过HBuilderX可视化界面,另一种是通过vue-cli命令行,也给出了两者的不同

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第1张图片
GF

总之,uni-app可以在其他编辑器里开发代码,然后通过命令行打包成相应平台的代码,打开官方平台运行,而HBuilder把库更新、打包、发布都集成了一遍,简化了操作。


对我来说,平时用惯了命令行运行打包,突然转换到使用编辑器的菜单栏来运行会相对感觉不舒畅 ( ̄▽ ̄)" ,所以我选择继续使用vscode来开发,命令行运行。


但是有点 的地方出现了,
运行官方给出的命令 vue create -p dcloudio/uni-preset-vue my-project 出现了证书问题

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第2张图片
image.png

尝试了几种网友给出的解法:
无标题.png

——都不行,还有的说是npm需要更新,或卸载重下,暂未尝试。

曲线救国法出现了!!!(上面的问题,在下才疏学浅,希望能人来解答)
打开支付宝小程序开发者工具
选取模板

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第3张图片
选取模板

新建项目

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第4张图片
新建项目

获得一个带完整编译器的模板

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第5张图片
获得一个uniapp模板

成功创建项目。


也许有人会思考为什么不直接在小程序开发者工具里开发,一个原因是惯用vscode,另一个主要原因是这个工具里没有命令行,编译靠右侧模拟器的手动操控刷新,因此编译时错误就不能及时看见解决。有时,只会在调试器里报这么一个错误:

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第6张图片
调试器展示通用错误

还无法溯源,只能凭经验思考一下刚刚哪些地方是否有不符合逻辑的地方了。(不过后面也有很多时候终端编译不报错,这里也会报错,主要是因为平时用vue和用uniapp这版本写小程序有些规范上不支持,这些区别后面讲)
还一个原因是频繁打包运行易崩溃


三、开发代码

h5转小程序,有两种思路,一种是重构为小程序代码,一种是嵌入h5页面。此处,我暂时先讲我重构遇到的问题。
下一篇:为什么不使用webview来实现h5转换为小程序。(记一下提醒我自己)
uniapp官方提供了从其他项目转uni-app的指南:转换指南(<(o)↗[良心啊!],少走很多弯路)

大多数问题都可以从此篇指南里解决,比如插件的替换和留下:
开头我列出了我在h5项目中使用的几个插件:

"vue-router": "^3.2.0", 
"vuex": "^3.4.0",
"axios": "^0.19.2",
"crypto-js": "^4.0.0", // 加密解密
"exif-js": "^2.3.0", // 获取图片元信息
"vant": "^2.10.0",
"vue-cli-plugin-svg-sprite": "^1.0.0",

其中vuexcrypto-js可以留下,不违反uni-app的规则,其余:

(1) vue-router => uni-simple-router

这个问题在指南中就有指出,uniapp不支持vue router

路由

于是按照指南,我选择了插件市场里下载量最高的轮子试了一下

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第7张图片
DCloud插件市场

一开始还没用起来,之前写项目太急了文档看得不仔细,再加上文档里也没有写需要同时再page.json里配置一份相同的路由,就总是报错;总结的时候重新试了一遍,其实挺好用的。
尝试了一下在不同生命周期里打印console.log('beforeCreate: ', this.$Route, this.$Router)

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第8张图片
$Route,$Router打印

ps:初始进入页面是不触发onLoad,onShow,onReady


总体来说,这个插件正如它自述所说的确是一个完全相似Vue-router的来操作路由插件(扩展了uniapp提供的路由操作,所以也不能缺少原有操作:在pages.json里写一份相同完整的路由,不过嵌套路由不支持),但是也局限于支付宝小程序的特性,如下:

  1. 支付宝小程序不支持组件作为页面使用(官方回复)
使用uniapp开发支付宝小程序(h5转支付宝小程序)_第9张图片
支付宝小程序不支持组件作为页面使用

简单尝试一下:

// index.vue
import test from '../test/test.vue'
    export default {
        components: {
            test
        },
        ...
// page.json
{
    "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }, {
            "path": "pages/test/test",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }
    ],
    ...

出现了报错:

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第10张图片
页面不作组件
尝试一:

即便按照模拟器中的提示,手动加上component: true

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第11张图片
手动加上component:true

依旧报错(看提示一下子看不懂)

报错
尝试二:

把组件置于components目录下(标准写法)

// index.vue
import test from '@/components/test/test.vue'
    export default {
        components: {
            test
        },
        ...
// page.json
{
    "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }, {
            "path": "components/test/test",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }
    ],
    ...

再手动加上component: true,运行后依旧报错,多了一行提示,invalid Component usage,指的是把components下的组件放进pages不符合规范。

报错
标准写法

自定义组件放components,不与pages扯上关系,不过正常h5开发中有时也有页面嵌套页面的效果,这种修改问题,可放下一篇讲。
下一篇:抽取相同组件和页面的共用方法mixins和公用样式css(记一下提醒我自己)
(为什么不用extends:哎,不一样啊つ﹏⊂,uniapp这版本的extends和mixins用法一样,template继承不了,会报错:继承的组件没有template标签)
示例:

// index.vue
import test from '@/components/test/test.vue'
    export default {
        components: {
            test
        },
        ...
// page.json
{
    "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path": "pages/index/index",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }, {
            "path": "pages/test/test",
            "style": {
                "navigationBarTitleText": "uni-app"
            }
        }
    ],
    ...

(2) axios => luch-request

指南:

ajax

市场:

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第12张图片
DCloud插件市场

好用并且相较于axios改动量小。(Toast是仿照vant用uniapp的api封装的组件)
示例:

// http.js
import Vue from "vue";
import Request from 'luch-request';
import serviceCode from "./serviceCode.js";
import Toast from "@/components/Toast.js";

const env = process.env.NODE_ENV;
const isDev = env === "development";
const merge = require("deepmerge");

// 创建请求实例
const req = new Request({
  timeout: 20000,
  withCredentials: false,
  baseURL: isDev ? `` : url // 接口ip
});

// 添加请求拦截器
req.interceptors.request.use(
  config => {
    if (
      !(/login/.test(config.url) || /get/.test(config.url) || /find/.test(config.url) || /login/.test(config.url))
    ) {
      Toast.loading({
        message: "请等待...",
        forbidClick: true,
        duration: 0,
        getContainer: "#app>.page-content>section"
      });
    }
    const authorization = Vue.prototype.localStorage.getItem("authorization");
    if (authorization) {
      config.header["Authorization"] = authorization;
    }
    return config;
  },
  err => {
    Toast.clear(true);
    // 对请求错误做些什么
    console.warn(err);
    return Promise.reject(err);
  }
);

// 添加响应拦截器
req.interceptors.response.use(
  res => {
    Toast.clear(true);
    if (serviceCode.includes(res.data.code)) {
      return res.data;
    } else {
      ... // 出错处理
      return Promise.reject(res.data);
    }
  },
  err => {
    Toast.clear(true);
    // 对响应错误做点什么
    ...
    return Promise.reject(err);
  }
);

export default {
  get (opts) {
    return new Promise((resolve, reject) => {
      const params = {
        method: "get"
      };
      req.request(merge(params, opts))
        .then(res => {
          resolve(res);
        })
        .catch(err => {
          reject(err);
        });
    });
  },
  post (opts) {
    return new Promise((resolve, reject) => {
      const params = {
        method: "post",
        headers: {
          "Content-Type": "application/json"
        }
      };
      req.request(merge(params, opts))
        .then(res => {
          resolve(res);
        })
        .catch(err => {
          reject(err);
        });
    });
  }
};

(3) exif-js => uni.getImageInfo(OBJECT)

虽说也都是普通的js代码,但是uni-app提供了获取图片信息的方法:uni.getImageInfo(OBJECT),虽然说最重要的orientation有局限性,但只是在h5。如果不考虑h5就不用担心;如果要考虑,用exif-js作为h5情况下的替代方案就行。

使用uniapp开发支付宝小程序(h5转支付宝小程序)_第13张图片
uni.getImageInfo(OBJECT)参数和返回

因为后续项目里关于这块的内容停止了,所以暂时没有完整的示例,就先借用了官方示例。
下一篇:支付宝小程序对比h5端的获取图片信息以及压缩图片(记一下提醒我自己)


简单示例:

my.chooseImage({
  count: 1,
  sizeType: ["compressed"],
  success: res => {
    uni.getImageInfo({
            src: res.tempFilePaths[0],
            success: function (image) {
                console.log(image.width);
                console.log(image.height);
            }
        });
  }
});

(4) vant => 无(后续看时间出一个出一个改版)


本来看到有人通过 Antmove 小程序转换器基于 Vant-Weapp 转换出了vant-ailapp,甚是欣喜 (^U^)ノ ,但是没想到啊,运行起来很多常用的组件都是问题 _〆(´Д` ) ,用了一两天改了一下,实在不行,暴脾气的我直接放弃,投奔了官方组件的怀抱。。。
还有一些官方组件也没有解决的Form+Field问题,只能自己在官方组件的基础上改改了,后续放github上,再过来更新地址。


放点不复杂的替代vant的功能,仅部分功能上的替代(不全,只换了用到的),不替代样式。
放点简单的Toast:

// Toast.js
class Toast {
  success (params) {
    uni.hideLoading();
    if (typeof params === 'object') {
      uni.showToast({
        title: params.message,
        icon: 'success',
        mask: params.forbidClick || false,
        duration: params.duration || 1000,
        position: 'center'
      });
    } else {
      uni.showToast({
        title: params,
        icon: 'success',
        mask: false,
        duration: 1000,
        position: 'center'
      });
    }
  }
  loading (params) {
    uni.hideLoading();
    if (typeof params === 'object') {
      uni.showToast({
        title: params.message,
        icon: 'loading',
        mask: params.forbidClick || false,
        duration: params.duration || 1000,
        position: 'center'
      });
    } else {
      uni.showToast({
        title: params,
        icon: 'loading',
        mask: false,
        duration: 1000,
        position: 'center'
      });
    }
  }
  fail (params) {
    uni.hideLoading();
    if (typeof params === 'object') {
      uni.showToast({
        title: params.message,
        icon: 'fail',
        mask: params.forbidClick || false,
        duration: params.duration || 1000,
        position: 'center'
      });
    } else {
      uni.showToast({
        title: params,
        icon: 'fail',
        mask: false,
        duration: 1000,
        position: 'center'
      });
    }
  }
  clear (params) {
    uni.hideLoading();
  }
}
export default new Toast();

简单的Dialog:

// Dialog.js
class Dialog {
  confirm (params) {
    if (typeof params === 'object') {
      return new Promise(function (resolve, reject) {
        uni.showModal({
          title: params.title || '对话框',
          content: params.message || '',
          cancelText: params.cancelButtonText || '取消',
          confirmText: params.confirmButtonText || '确定',
          success: (res) => {
            if (res.confirm) {
              resolve();
            } else if (res.cancel) {
              reject();
            }
          }
        });
      });
    } else {
      return new Promise(function (resolve, reject) {
        uni.showModal({
          title: '对话框',
          content: params,
          cancelText: '取消',
          confirmText: '确定',
          success: (res) => {
            if (res.confirm) {
              resolve();
            } else if (res.cancel) {
              reject();
            }
          }
        });
      });
    }
  }
}
export default new Dialog();

(5)vue-cli-plugin-svg-sprite => background-image/

小程序里没有svg标签,都换成图片方式引入了。

四、坑点总结

不知不觉写的有点长。另起一篇吧:uniapp坑点(支付宝小程序)

你可能感兴趣的:(使用uniapp开发支付宝小程序(h5转支付宝小程序))