小程序 + vant
vant
下載
npm init -ynpm 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.wxssaddGlobalClass: true,},/*** 組件的屬性列表*/properties: {show: {type: Boolean,value: false,},},/*** 組件的初始數據*/data: {},// 監聽observers: {show: function (newVal) {console.log("newVal", newVal);},},// // 生命周期函數,可以為函數,或一個在methods段中定義的方法名lifetimes: {// 可以使用 setDataattached: 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
<view class="relative" wx:if="{{desc}}"><text class="{{showMore?'x-ellipsis--l3':''}} " style="line-height: {{lineHight}}px" ><text catchtap="handleLink">{{desc}}</text><text class="absolute primary more" bindtap="handleReadMore" wx:if="{{showMore}}">查看全部</text><text class="absolute primary more" bindtap="handleHideMore" wx:if="{{isExceed && !showMore}}">收起</text></text>
</view>
<block wx:else><slot></slot>
</block>
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"><textareaclass="absolute left-0 top-0"focus="{{focus}}"bindblur="onBlur"model:value="{{value}}"disable-default-paddingmaxlength="{{-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);},
});