小程序 vant 项目记录总结 使用 scss 分享 订阅消息 wxs 分包 echarts图表 canvas getCurrentPages页面栈

小程序 + vant

vant

下载

npm init -y

npm i @vant/weapp -S --production

修改 app.json

将 app.json 中的 “style”: “v2” 去除

修改 project.config.json

{
  ...
  "setting": {
    ...
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./"
      }
    ]
  }
}

构建 npm 包

打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件。

van-dialog

before-close 回调函数使用
不需要 confirm,cancel 事件

<van-dialog use-slot show="{{ show }}" before-close="{{ beforeClose }}">van-dialog>
Page({
    onLoad() {
        this.setData({
            beforeClose: (action) => this.confirmScore(action),
        });
    },

    confirmScore(action) {
        return new Promise((resolve, reject) => {
            if (action === "cancel") return resolve(true);
            if (action === "confirm") {
                if (...) return resolve(false);
                return resolve(true);
            }
        });
    },
})

小程序

使用 typescript、less、sass

编译插件配置,目前支持编译插件有 typescript、less、sass
project.config.json

{
    "setting": {
        "useCompilerPlugins": [
            "typescript",
            "sass"
        ]
    }
}

表示项目支持直接使用 typescript 和 sass

.wxss 文件命名 .scss

setData 多种赋值写法

const index = 0;

data: {
    list: [
        {
            page: 1,
            limit: 10,
            data: [],
            finished: false,
        },
    ],
    obj: {
        name:'ls',
    },
    name: 'ls'
},
this.setData({
    name: 'xr',
    obj: {},
    'obj.name': 'xr',
    'list[0].finished': true,
    `list[${index}].data`: [],
     [`list[${index}].data`]: []
})

getCurrentPages 页面栈使用

const pages = getCurrentPages();
const prePage = pages[pages.length - 2];

// 获取上一页数据
console.log("prePage", prePage?.data.detail);
// 调用上一页方法
prePage?.save();
// 修改上一页数据
prePage.setData({
	"detail.name": "xr",
});

request 请求封装

封装 request
utils/http.js

import getBaseUrl from "./config";
import log from "./log";

class CustomRequestInstance {
	static instance = null;
	static getInstance() {
		if (!this.instance) {
			this.instance = new CustomRequestInstance();
		}
		return this.instance;
	}
	request({ url = "", method = "get", data = {}, headers = {} }) {
		return new Promise((resolve, reject) => {
			wx.request({
				url: getBaseUrl() + url,
				header: {
					"content-type": "application/json",
					Authorization: `Bearer ${wx.getStorageSync("token")}`,
					...headers,
				},
				method: method.toUpperCase(),
				data: {
					...data,
				},
				success: function (res) {},
				fail: function (err) {
					log.error(`request-${url}-${err}`);
					reject(err);
				},
			});
		});
	}
}

export default CustomRequestInstance;

封装 api
services/common.js

import Services from "../utils/http";

const http = Services.getInstance();

class CommonServices {
	// 获取城市
	async getCity() {
		const res = await http.request({ url: "/city" });
		return res;
	}
}

export default new CommonServices();

使用

import CommonServices from "../../../services/common";

const res = await CommonServices.getCity();

upload 封装

import getUrl from "./config";

/**
 * uploadFile 封装
 * @param {*} Object { name = "file", filePath, ...rest }
 *
 * @see [https://developers.weixin.qq.com/miniprogram/dev/api/media/video/wx.chooseMedia.html](https://developers.weixin.qq.com/miniprogram/dev/api/media/video/wx.chooseMedia.html)
 */
export function customUpload({ name = "file", filePath, ...rest }) {
	return new Promise((resolve, reject) => {
		wx.uploadFile({
			url: `${getUrl()}/upload/image`,
			filePath,
			name,
			header: {
				Authorization: `Bearer ${wx.getStorageSync("token")}`,
			},
			...rest,
			success(res) {
				resolve(JSON.parse(res.data));
			},
			fail(error) {
				reject(error);
			},
		});
	});
}

组件

components/test/test.json

{
  "component": true,
  "usingComponents": {
    "van-icon": "@vant/weapp/icon/index"
  }
}

components/test/test.wxml

<view wx:if="{{show}}" class="mask">
	<van-icon class="remove" name="cross" catchtap="handleClose" />
</view>

components/test/test.js

Component({
	options: {
		// 在组件定义时的选项中启用多slot支持
		multipleSlots: true,
		// 使用全局 app.wxss
		addGlobalClass: true,
	},

	/**
	 * 组件的属性列表
	 */
	properties: {
		show: {
			type: Boolean,
			value: false,
		},
	},

	/**
	 * 组件的初始数据
	 */
	data: {},

	// 监听
	observers: {
		show: function (newVal) {
			console.log("newVal", newVal);
		},
	},

	// // 生命周期函数,可以为函数,或一个在methods段中定义的方法名
	lifetimes: {
		// 可以使用 setData
		attached: function () {
			console.log("attached");
		},
		moved: function () {
			console.log("moved");
		},
		detached: function () {
			console.log("detached");
		},
	},

	//  // 组件所在页面的生命周期函数
	pageLifetimes: {
		// 页面进入
		show: function () {
			console.log("show");
		},
		// 页面离开
		hide: function () {
			console.log("hide");
		},
		resize: function () {
			console.log("resize");
		},
	},

	/**
	 * 组件的方法列表
	 */
	methods: {
		handleClose() {
			this.triggerEvent("close", { flag: false });
		},
	},
});
  • 实现默认插槽
Component({
	properties: {
		useDefaultSlot: {
			type: Boolean,
			value: false,
		},
	},
});

components/test/test.js

<view>
	<view wx:if="{{ !useDefaultSlot }}">默认值</view>
	<slot wx:else></slot>

	<slot name="after"></slot>
</view>
{
	"usingComponents": {
		"x-text": "/components/test/test"
	}
}
<x-text useDefaultSlot>
	<view>默认插槽</view>
	<view slot="after">after 插槽</view>
</x-text>
  • 向外提供样式类
<view class="custom-class"></view>
Component({
	externalClasses: ["custom-class"],
});
<x-text custom-class="text-14px"></x-text>
  • 获取组件实例

可在父组件里调用 this.selectComponent ,获取子组件的实例对象。
父组件

Page({
	getChildComponent: function () {
		const child = this.selectComponent(".my-component");
	},
});

wxs 使用

在当前 wxml 中

<view wx:if="{{util.isHas(idList, userId)}}"></view>

<wxs module="util">
function isHas(arr, val) {
    return arr.indexOf(val) >= 0
}
module.exports.isHas = isHas
</wxs>

单独封装方法
utils/utils.wxs

module.exports = {
	formatNums: function (val) {
		if (val >= 1000) {
			val = (val / 1000) + 'K'
		}
		return val
	},
}

<wxs module="tools" src="/utils/utils.wxs"></wxs>

{{tools.formatNums(item.type)}}

获取元素信息 wx.createSelectorQuery()

如果在组件中,使用 this.createSelectorQuery()

const query = wx.createSelectorQuery();
query.select(".content").boundingClientRect();
query.exec((res) => {});

获取列表数据/加载更多/下拉刷新

data: {
    list: [
        {
            page: 1,
            limit: 10,
            data: [],
            finished: false,
        },
    ],
},

onReachBottom() {
    this.getData();
},
async onPullDownRefresh() {
    await this.getData(true);
    wx.stopPullDownRefresh();
},
async getData(refresh = false) {
    try {
        const index = 0;
        let { page, limit, data, finished } = this.data.list[index];

        if (refresh) {
            page = 1;
            finished = false;
        }
        if (finished) return;

        const res = await SkillServices.getFairList(page, limit);

        let list = [];
        if (res?.data?.length < limit) finished = true;
        else finished = false;

        if (refresh) list = res?.data;
        else list = [...data, ...res.data];

        this.setData({
            [`list[${index}].data`]: list,
            [`list[${index}].page`]: page + 1,
            [`list[${index}].finished`]: finished,
        });
    } catch (error) {
        console.log(error);
    }
},

实现文本隐藏展示查看更多功能

text-ellipsis.wxml


	{{desc}}查看全部
		收起
	


	

text-ellipsis.js

// pages/mine/visit/components/text-ellipsis/text-ellipsis.js
Component({
	options: {
		addGlobalClass: true,
	},
	properties: {
		lineHight: {
			type: Number,
			value: 22,
		},
		desc: {
			type: null,
			value: "",
		},
	},

	data: {
		showMore: false,
		isExceed: false, // 是否超过三行
	},

	observers: {
		desc(newValue) {
			// 如果当前被截断则直接返回
			if (this.data.showMore) return;
			if (newValue) this.init();
		},
	},

	methods: {
		handleLink() {
			this.triggerEvent("link");
		},
		handleHideMore() {
			this.setData({ showMore: true });
		},
		handleReadMore() {
			this.setData({ showMore: false });
		},
		init() {
			const { lineHight } = this.data;
			let showMore = false;
			let isExceed = false;
			var query = this.createSelectorQuery();
			query.select(".content").boundingClientRect();
			query.exec((res) => {
				var height = res[0]?.height;
				if (!height) return;
				var line = height / lineHight;
				if (line > 3) {
					showMore = true;
					isExceed = true;
				} else {
					showMore = false;
					isExceed = false;
				}
				this.setData({ showMore, isExceed });
			});
		},
	},
});
.more {
	bottom: 0;
	right: 0;
	background: #fff;
	padding-left: 10rpx;
}
.relative {
	position: relative;
}
.absolute {
	position: absolute;
}
.primary {
	color: var(--primary);
}

引入组件

{
	"usingComponents": {
		"x-text-ellipsis": "/components/text-ellipsis/text-ellipsis"
	}
}
<x-text-ellipsis desc="{{userInfo.desc}}">
	<view class="text-sm text-aaa">暂未填写简介</view>
</x-text-ellipsis>

订阅消息封装

export const requestSubscribeMessage = (tmplIds = ["MLCQvuq6kWY-jbH8Cxn-veoPZ6gJ8QTWiBpMV96mjs0"]) => {
	wx.requestSubscribeMessage({
		tmplIds,
		success(res) {
			console.log("requestSubscribeMessage res", res);
			// MLCQvuq6kWY-jbH8Cxn-veoPZ6gJ8QTWiBpMV96mjs0: "reject" 取消
			// MLCQvuq6kWY-jbH8Cxn-veoPZ6gJ8QTWiBpMV96mjs0: "accept" 同意
			tmplIds.forEach((tmplId) => {
				if (res[tmplId] === "accept") console.log(tmplId, "同意了");
				else if (res[tmplId] === "reject") console.log(tmplId, "拒绝了");
			});
		},
		fail(err) {
			console.log("requestSubscribeMessage err", err);
		},
	});
};

分享

onShareAppMessage(){
    return {
        title: '**',
        path: '/pages**',
        imageUrl: '/static/share.png'
    }
}

分包

others/pages/mine/mine.wxml
app.json

    ...
    "subPackages": [
        {
            "root": "others",
            "pages": ["pages/mine/mine"]
        }
    ],

防抖、节流

css

动画
/* pages/idea/form/form.wxss */

@keyframes opacity-show {
	0% {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}
@keyframes opacity-hide {
	0% {
		opacity: 1;
	}

	to {
		opacity: 0;
	}
}

@keyframes scale-show {
	0% {
		transform: scale(0);
	}

	to {
		transform: scale(1);
	}
}
@keyframes scale-hide {
	0% {
		transform: scale(1);
	}

	to {
		transform: scale(0);
	}
}
<view style="animation: opacity-show 2s ease,scale-show 2s ease;"></view>
.x-ellipsis {
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
.x-ellipsis--l2,
.x-ellipsis--l3 {
	-webkit-box-orient: vertical;
	display: -webkit-box;
	overflow: hidden;
	text-overflow: ellipsis;
}
.x-ellipsis--l2 {
	-webkit-line-clamp: 2;
}
.x-ellipsis--l3 {
	-webkit-line-clamp: 3;
}

.arrow::after {
	content: "";
	width: 15rpx;
	height: 15rpx;
	border-top: 3rpx solid ##ccc;
	border-right: 3rpx solid ##ccc;
	transform: rotate(45deg);
}

/* 数字、字母过长不换行 */
.break-all {
	word-break: break-all;
}

scroll-view::-webkit-scrollbar {
	width: 0;
	height: 0;
	color: transparent;
}
修改 vant css 变量
page {
	--van-picker-confirm-action-color: #08bebe;
}
修改 input placeholder 样式
<input value="{{ keyword }}" placeholder-class="placeholder-class" placeholder="请输入" bindinput="handleInput" />
.placeholder-class {
	color: #bbbbbb;
}

textarea 设置行高后,光标与 placeholder 不对齐
解决方案:使用 text 替换 placeholder

iPhone 13 pro 遇到的问题:
text 不要放在 textarea 标签内
定位后,给 text 一个事件,自动获取焦点

<view class="relative">
	<textarea
		class="absolute left-0 top-0"
		focus="{{focus}}"
		bindblur="onBlur"
		model:value="{{value}}"
		disable-default-padding
		maxlength="{{-1}}"
	/>
	<text class="text-aaa text-14px leading-28px absolute left-0 top-0" bindtap="onFocus" wx:if="{{!value}}">
		提示:请输入内容请输入内容请输入内容请输入内容请输入内容
	</text>
</view>
Page({
	data: {
		focus: false,
		value: "",
	},
	onFocus() {
		this.setData({ focus: true });
	},
	onBlur() {
		this.setData({ focus: false });
	},
});

Ios 底部安全距离

page {
	--ios-safe-bottom-low: constant(safe-area-inset-bottom); /* 兼容 iOS<11.2 */
	--ios-safe-bottom-higt: env(safe-area-inset-bottom); /* 兼容iOS>= 11.2 */
}
.safe-area-inset-bottom {
	padding-bottom: var(--ios-safe-bottom-low);
	padding-bottom: var(--ios-safe-bottom-higt);
}
setFormValue(value, num = undefined) {
    value = value.trim();
    if (num) value = value.slice(0, num);
    else value = value.slice(0);
    return value;
},

echarts-for-weixin

动态设置数据如下

echarts-for-weixin

xx.json

{
  "usingComponents": {
    "ec-canvas": "../../ec-canvas/ec-canvas"
  }
}

xx.wxml

<view class="container">
    <ec-canvas id="mychart-dom-bar" canvas-id="mychart-bar" ec="{{ ec }}"></ec-canvas>
  </view>

xx.wxss

ec-canvas {
    width: 100%;
    height: 100%;
  }

xx.js

import * as echarts from '../../ec-canvas/echarts';

let chart = null;
let option;
function initChart(canvas, width, height, dpr) {
  chart = echarts.init(canvas, null, {
    width: width,
    height: height,
    devicePixelRatio: dpr // 像素
  });
  canvas.setChart(chart);

  option = {
    xAxis: {
      type: "category",
      data: ["今日已完成", "本周已完成", "本月已完成", "本季度已完成"],
    },
    yAxis: {
      type: "value",
    },
    series: [
      {
        data: [10, 20, 150, 8],
        type: "bar",
      },
    ],
  };
  chart.setOption(option);
  return chart;
}

Page({
  data: {
    ec: {
      onInit: initChart
    }
  },



  onLoad() {
    setTimeout(() => {
        option.series[0].data = [10, 20, 150, 8, 70]
        chart.setOption(option);
    }, 2000);
  },
});

百度人脸对比

common/baidu/config.js


const configArrary = [
  {
    grant_type: 'client_credentials',
    client_id: 'xxx',
    client_secret: 'xxxx',
    access_token: '',
  },
]

module.exports = configArrary

common/baidu/baidu.js

const configArrary = require("config.js");

class Baidu {
    errorCode = {
        222202: "图片中没有人脸或者图片质量太差",
        222203: "无法解析人脸;图片质量太差",
        223123: "质量检测未通过 左脸遮挡程度过高",
        223122: "质量检测未通过 右眼遮挡程度过高",
        223121: "质量检测未通过 左眼遮挡程度过高",
        223120: "活体检测未通过",
        223124: "质量检测未通过 右脸遮挡程度过高",
        223125: "质量检测未通过 下巴遮挡程度过高",
        223126: "质量检测未通过 鼻子遮挡程度过高",
        223127: "质量检测未通过 嘴巴遮挡程度过高",
        223114: "人脸模糊",
    };

    getTokenAsync() {
        return new Promise((resolve, reject) => {
            const that = this;
            wx.request({
                url: "https://aip.baidubce.com/oauth/2.0/token",
                data: {
                    grant_type: that.config.grant_type,
                    client_id: that.config.client_id,
                    client_secret: that.config.client_secret,
                },
                success(res) {
                    return resolve(res.data.access_token);
                },
                fail(err) {
                    reject(err);
                },
            });
        });
    }
 
    async faceMatch(image) {
        const that = this;
        const newToken = await this.getTokenAsync();
        return new Promise((resolve, reject) => {
            wx.request({
                method: "POST",
                url: "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=" + newToken,
                data: [...image],
                success(res) {
                    res = res.data;
                    if (res.error_code !== 0) {
                        reject(
                            that.errorCode[String(res.error_code)]
                                ? that.errorCode[String(res.error_code)]
                                : "人脸识别失败,请重试!" + String(res.error_code)
                        );
                    } else {
                        if (res.result.score < 80) {
                            reject("人脸不匹配,请重试!");
                        }
                    }
                    resolve(res);
                },
                fail(err) {
                    reject(err);
                },
            });
        });
    }
}

module.exports = Baidu;

使用

const Baidu = require("../../../common/baidu/baidu.js");

let  baidu = new Baidu();

await baidu.faceMatch([
    {
        image: base64Img,
        face_type: "LIVE",
        image_type: "BASE64",
        quality_control: "LOW",
        liveness_control: "NORMAL",
    },
    {
        image: userDetail.faceImg,
        face_type: "LIVE",
        image_type: "URL",
        quality_control: "LOW",
        liveness_control: "NONE",
    },
]);

你可能感兴趣的:(小程序,vant,小程序)