基于uni-app实现微信小程序 — vue3+ts+sass
作者:逆风翱翔的青鴍
项目背景
基于uni-app实现微信小程序,使用框架:vue3+ts+sass
为什么要自定义picker?
原生小程序不支持自定义样式
该自定义组件支持:
自定义数据
自定义样式
联动
思路:
使用小程序嵌入页面的滚动选择器picker-view实现选择滚动。
在pick-view中使用picker-view-column,可以使其孩子节点的高度自动设置成与picker-view的选中框的高度一致,并不在页面中显示
picker-view介绍:
属性 | 类型 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|
value | Array.<number> | 否 | 数组中的数字依次表示 picker-view 内的 picker-view-column 选择的第几项(下标从 0 开始),数字大于 picker-view-column 可选项长度时,选择最后一项。 | 1.0.0 |
indicator-style | string | 否 | 设置选择器中间选中框的样式 | 1.0.0 |
indicator-class | string | 否 | 设置选择器中间选中框的类名 | 1.1.0 |
mask-style | string | 否 | 设置蒙层的样式 | 1.5.0 |
mask-class | string | 否 | 设置蒙层的类名 1.5.0 | |
bindchange | eventhandle | 否 | 滚动选择时触发change事件,event.detail = {value};value为数组,表示 picker-view 内的 picker-view-column 当前选择的是第几项(下标从 0 开始) | 1.0.0 |
bindpickstart | eventhandle | 否 | 当滚动选择开始时候触发事件 | 2.3.1 |
bindpickend | eventhandle | 否 | 当滚动选择结束时候触发事件 | 2.3.1 |
实现:
1.页面布局:
<template>
<view :class="['fh-full-box', isOpen ? 'fh-cur' : '']">
<view class="fh-picker">
<view class="fh-picker-header" :style="pickerHeaderStyle">
<view @click="cancle">
<text :style="cancelStyle">{{ cancelText }}</text>
</view>
<text :style="titleStyle">{{ titleText }}</text>
<view @click="sure">
<text :style="sureStyle">{{ sureText }}</text>
</view>
</view>
<picker-view
:value="value"
class="fh-picker-content"
@pickstart="pickStart"
@change="pickChange"
@pickend="pickEnd"
:indicator-style="indicatorStyle"
>
<picker-view-column
v-for="(items, index) in columnsData"
:key="index"
>
<view v-for="(item, index) in items" :key="index">
<text class="fh-line">{{
isUseKeywordOfShow ? item[keyWordsOfShow] : item
}}</text>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</template>
复制代码
样式:
由于组件引入后是覆盖着页面上面的会影响正常页面的点击,所以使用了pointer-events。
下面介绍一下pointer-events
pointer-events用来指定在什么情况下元素可以成为鼠标事件的target
属性有很多值,但是对于浏览器来说,只有auto和none两个值可用,所以介绍一下auto和none
auto——效果和没有定义pointer-events属性相同,鼠标不会穿透当前层。
none——元素永远不会成为鼠标事件的target(目标)
支持:Firefox 3.6+和chrome 2.0+ 以及safari 4.0+都支持这个CSS3属性,IE6/7/8/9都不支持,Opera在SVG中支持该属性但是HTML中不支持。
<style lang="scss" scoped>
.fh-full-box {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.4);
transition: all 0.4s ease-in-out 0;
pointer-events: none;
opacity: 0;
.fh-picker {
position: absolute;
left: 0;
bottom: -470rpx;
display: flex;
flex-direction: column;
width: 100%;
height: 470rpx;
background: #ffffff;
transition: all 0.4s ease-in-out 0;
}
.fh-picker-header {
height: 20%;
box-sizing: border-box;
padding: 0 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eeeeee;
view {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
text {
font-size: 36rpx;
}
}
}
.fh-picker-content {
flex-grow: 1;
view {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
text {
font-size: 36rpx;
}
}
.fh-line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.fh-full-box.fh-cur {
opacity: 1;
pointer-events: auto;
.fh-picker {
bottom: 0;
}
}
</style>
复制代码
实现逻辑:
接收父组件传值props
props: {
scrollType: {
// "link": scroll间联动 "normal": scroll相互独立
type: String,
value: "normal",
},
titleText: {
// 标题文案
type: String,
},
cancelText: {
// 取消按钮文案
type: String,
},
sureText: {
// 确定按钮文案
type: String,
},
listData: {
//数据源
type: Array,
default: () => [],
},
defaultPickData: {
//默认选择
type: Array,
default: () => [],
},
pickerHeaderStyle: String, // 标题栏样式 view
sureStyle: String, // 标题栏确定样式 text
cancelStyle: String, // 标题栏取消样式 text
titleStyle: String, // 标题栏标题样式 view
indicatorStyle: {
// 选中框样式
type: String,
default: "height:48px;",
},
keyWordsOfShow: {
// 展示关键字段
type: String,
default: "name",
},
isShowPicker: {
// 是否展示弹窗
type: Boolean,
default: false,
},
},
复制代码
定义响应式数据:
const DATA = reactive<BOOTOMPOPDATA>({
columnsData: [], // 数组
value: [], // 选中的value
isOpen: false, // 是否显示picker
isUseKeywordOfShow: false, // 是否根据关键字展示
scrollEnd: true, // 滚动是否结束
tempValue: [], // 选中数据的索引
});
// 用ts定义接口约束数据
type dataARR = Array<string | number | object>;
export interface BOOTOMPOPDATA {
columnsData: dataARR,
value: dataARR,
isOpen: boolean,
isUseKeywordOfShow: boolean,
scrollEnd: boolean,
tempValue: dataARR,
}
复制代码
一些相关按钮操作事件:
/**
* @description: 关闭弹窗
*/
function closePicker() {
DATA.isOpen = false;
}
/**
* @description: 打开弹窗
*/
function openPicker() {
// 保存重新打开时,选中上次保存所选中的内容
setDefault();
DATA.isOpen = true;
}
/**
* @description: 当滚动选择开始时候触发事件
*/
function pickStart() {
DATA.scrollEnd = false;
}
/**
* @description: 当滚动选择结束时候触发事件
*/
function pickEnd() {
DATA.scrollEnd = true;
}
/**
* @description: 点击取消按钮
*/
function cancle() {
closePicker();
context.emit("cancleFn");
}
/**
* @description: 点击确认按钮
*/
function sure() {
const { scrollEnd, tempValue } = DATA;
if (!scrollEnd) return;
const backData = getBackDataFromValue(tempValue);
closePicker();
context.emit("sureFn", {
choosedData: backData,
choosedIndexArr: tempValue,
});
}
复制代码
通过判断scrollType来进行数据初始化处理
function setDefault() {
const { scrollType } = props;
const { tempValue } = DATA;
let listData: any[] = props.listData;
let defaultPickData: any[] = props.defaultPickData;
switch (scrollType) {
case "normal":
if (isPlainObject(listData[0][0])) {
DATA.isUseKeywordOfShow = true;
}
if (
Array.isArray(defaultPickData) &&
defaultPickData.length > 0
) {
DATA.tempValue = defaultPickData;
}
DATA.columnsData = listData;
DATA.value = defaultPickData;
break;
case "link":
let columnsData = [];
if (
Array.isArray(defaultPickData) &&
defaultPickData.length > 0
) {
if (defaultPickData.every((v) => isPlainObject(v))) {
const key = Object.keys(defaultPickData[0])[0];
const arr: any[] = [];
getIndexByIdOfObject(
listData,
defaultPickData,
key,
arr
);
defaultPickData = arr;
}
let tempI = 0;
do {
columnsData.push(getColumnData(listData));
listData =
listData[defaultPickData[tempI]].children;
tempI++;
} while (listData);
} else {
do {
tempValue.push(0);
columnsData.push(getColumnData(listData));
listData = listData[0].children;
} while (listData);
}
DATA.tempValue = defaultPickData;
DATA.isUseKeywordOfShow = true;
DATA.columnsData = columnsData;
DATA.value = defaultPickData;
break;
}
}
/**
* @description: 根据id获取索引
*/
function getIndexByIdOfObject(
listData: any[],
idArr: any[],
key: string,
arr: any[]
): any {
if (!Array.isArray(listData)) return;
for (let i = 0, len = listData.length; i < len; i++) {
if (listData[i][key] === idArr[arr.length][key]) {
arr.push(i);
return getIndexByIdOfObject(
listData[i].children,
idArr,
key,
arr
);
}
}
}
/**
* @description: 过滤数据,不要children
*/
function getColumnData(arr: Array<object>) {
return arr.map((v) => fomateObj(v));
}
function fomateObj(o: any) {
const temp: any = {};
for (const k in o) {
k !== "children" && (temp[k] = o[k]);
}
return temp;
}
复制代码
滚动选择时触发的change事件,根据scrollType判断是否进行联动处理
/**
* @description: 滚动选择时触发change事件
*/
function pickChange(e: any) {
const { scrollType } = props;
const { tempValue } = DATA;
let val = e.detail.value;
switch (scrollType) {
case "normal":
DATA.tempValue = val.concat();
DATA.value = val.concat();
break;
case "link":
const tempArray: any[] = [];
val = validate(val);
if (val.length > 1) {
val.slice(0, val.length - 1).reduce(
(t: any, c: any) => {
let v;
if (t.length < c || t.length == c) {
v = t[t.length - 1].children;
} else {
v = t[c].children;
}
tempArray.push(getColumnData(v));
return v;
},
props.listData
);
//
var columnsData = [DATA.columnsData[0], ...tempArray];
// 设置value关联
var compareIndex = getScrollCompareIndex(
tempValue,
val
);
if (compareIndex > -1) {
let tempI = 1;
while (val[compareIndex + tempI] !== undefined) {
val[compareIndex + tempI] = 0;
tempI++;
}
}
DATA.tempValue = val.concat();
DATA.columnsData = columnsData;
DATA.value = val;
break;
}
}
}
/**
* @description: 联动,滚动时判断子索引是否是在0,不是则返回,进行处理
*/
function getScrollCompareIndex(arr1: any[], arr2: any[]) {
let tempIndex = -1;
for (let i = 0, len = arr1.length; i < len; i++) {
if (arr1[i] !== arr2[i]) {
tempIndex = i;
break;
}
}
return tempIndex;
}
/**
* @description: 当默认选择的子元素下标不是0,当改变第一列时,所有的子元素下标修改为0
*/
function validate(val: number[]) {
const { tempValue } = DATA;
const len = tempValue.length;
for (let i = 0; i < len; i++) {
if (tempValue[i] != val[i] && val[i] == 0) {
const arr = val.splice(i + 1, len - 1);
for (let k = 0; k < arr.length; k++) {
val.push(0);
}
}
}
return val;
}
复制代码
判断是不是object方法:isPlainObject()
/**
* @description: 获取数据类型
*/
function _typeof(obj: any) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
/**
* @description: 判断是否object
*/
export function isPlainObject(obj: any) {
return _typeof(obj) === 'object';
}
复制代码
使用:
参数说明
name | type | required | default | description |
---|---|---|---|---|
isShowPicker | Boolean | 是 | false | 显示隐藏picker |
listData | Array | 是 | [] | 数据源 |
scrollType | String | 否 | “normal” | |
cancelText | String | 否 | “” | 取消按钮文案 |
sureText | String | 否 | “” | 确认按钮文案 |
sureStyle | String | 否 | “” | 确认按钮样式 |
cancelStyle | String | 否 | “” | 取消按钮样式 |
titleText | String | 否 | “” | 标题文案 |
titleStyle | String | 否 | “” | 标题样式 |
pickerHeaderStyle | String | 否 | “” | 标题栏样式 |
defaultPickData | Array | 否 | [] | 默认选择数据 |
keyWordsOfShow | String | 否 | “name” | 当 listData 的的每一个成员,是由对象组成的数组时,keyWordsOfShow 作为对象的 key,其 value 用于显示;或者当 picker=’link’时,供显示的 key |
cancleFn | Funciton | 否 | “” | 点击取消触发的事件 |
sureFn | Funciton | 否 | “” | 点击确定触发的事件 |
在页面中引入该组件
import bottomPop from "@/components/bottomPop/bottomPop.vue";
<bottom-pop
:isShowPicker="showPicker2"
:listData="listData2"
scrollType="normal"
cancelText="取消"
sureText="确认"
sureStyle="color:green"
cancelStyle="color:red"
titleText="多列非联动"
titleStyle="color:orange"
pickerHeaderStyle="height:160rpx;"
:defaultPickData="defaultPickData2"
keyWordsOfShow="str"
@cancleFn="cancleCallBack2"
@sureFn="sureCallBack2"
></bottom-pop>
复制代码
点击确认,取消事件
function sureCallBack2(e: any) {
console.log("点击了确认");
}
function cancleCallBack2() {
console.log("点击了关闭");
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END