npm init -y
npm i @vant/weapp -S --production
将 app.json 中的 “style”: “v2” 去除
{
...
"setting": {
...
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
]
}
}
打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件。
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
project.config.json
{
"setting": {
"useCompilerPlugins": [
"typescript",
"sass"
]
}
}
表示项目支持直接使用 typescript 和 sass
.wxss 文件命名 .scss
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`]: []
})
const pages = getCurrentPages();
const prePage = pages[pages.length - 2];
// 获取上一页数据
console.log("prePage", prePage?.data.detail);
// 调用上一页方法
prePage?.save();
// 修改上一页数据
prePage.setData({
"detail.name": "xr",
});
封装 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();
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");
},
});
在当前 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)}}
如果在组件中,使用 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"]
}
],
/* 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;
}
page {
--van-picker-confirm-action-color: #08bebe;
}
<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 });
},
});
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
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",
},
]);