基于uni-app实现微信小程序 — vue3+ts+sass

基于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("点击了关闭");
}
复制代码

技术分享宣传图@3x.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享