Vue3 + TS + Sass Micro program Based on UNI-App

Cheong Soars against the wind 鴍

Project background

Realize wechat applets based on UNI-App, using the framework vue3+ TS + SASS

Why custom picker?

Native applets do not support custom styles

This custom component supports:

Custom data Custom style linkage

Ideas:

Select scrolling is implemented using picker-View, a scroll selector embedded in the page by applets. Using picker-view-column in a picker-view automatically sets the height of the child node to the height of the picker-View check box and does not show it in the page

Picker – view is introduced:
attribute type mandatory instructions Minimum version
value Array.<number> no The numbers in the array indicate the number of items (subscripts starting from 0) selected by the picker-View-column in the picker-View. If the number is greater than the optional length of the picker-view-column, the last item is selected. 1.0.0
indicator-style string no Sets the style of the checkbox in the middle of the selector 1.0.0
indicator-class string no Sets the class name of the box in the middle of the selector 1.1.0
mask-style string no Sets the style of the mask 1.5.0
mask-class string no Set the mask class name to 1.5.0
bindchange eventhandle no The change event, event.detail = {value}, is emitted while scrolling through the selection. Value is an array, indicating what item is currently selected for the picker-view-column in picker-view (subscripts start at 0) 1.0.0
bindpickstart eventhandle no Trigger event when scroll selection begins 2.3.1
bindpickend eventhandle no Raises the event when the scrolling selection ends 2.3.1

Implementation:

1. Page Layout:
<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>
Copy the code
Style:

Since the component is introduced to overwrite the clicks on the page that will affect the normal page, pointer-events is used. The pointer-events attribute, which is used to specify under which circumstances an element can be a mouse event, has many values, but for browsers only two values are available: auto and None. So auto and None auto — the effect is the same as if the pointer-events attribute was not defined, and the mouse does not penetrate the current layer. None — The element will never be supported as a target for mouse events: This CSS3 property is supported by Firefox 3.6+, Chrome 2.0+, and Safari 4.0+, not by IE6/7/8/9, and supported by Opera in SVG but not 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.4 s 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.4 s 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>
Copy the code
Implementation logic:

Receive props from the parent component

props: {
    scrollType: {
        // "link": Scroll linkage"normal": scroll independent type: String, value:"normal",}, titleText: {// titleText type: String,}, cancelText: {// cancel button text type: String,}, sureText: {// confirm button text type: String,}, listData: {// Data source type: Array, default: () => [],}, defaultPickData: {// Default selection type: Array, default: () => [],}, pickerHeaderStyle: String, // String, // title bar unstyle text titleStyle: String, // title bar titleStyle view indicatorStyle: {// box style type: String, default:"height:48px;"}, keyWordsOfShow: {// Display key fields type: String, default:"name"Type: Boolean, default: false,},}, isShowPicker: {// whether to show pop-upsCopy the code

Define reactive data:

const DATA = reactive<BOOTOMPOPDATA>({
    columnsData: []./ / array
    value: [].// Select value
    isOpen: false.// Whether to display picker
    isUseKeywordOfShow: false.// Whether to display by keyword
    scrollEnd: true.// Whether scrolling ends
    tempValue: [].// Select the index of the data
});
// Use ts to define the interface constraint data
type dataARR = Array<string | number | object>;
export interface BOOTOMPOPDATA {
    columnsData: dataARR,
    value: dataARR,
    isOpen: boolean.isUseKeywordOfShow: boolean.scrollEnd: boolean.tempValue: dataARR,
}
Copy the code

Some related button action events:

/ * * *@description: Close popover */
function closePicker() {
    DATA.isOpen = false;
}

/ * * *@description: Open popover */
function openPicker() {
    // Save to open again, select the selected content saved last time
    setDefault();
    DATA.isOpen = true;
}

/ * * *@description: Raises the event */ when the scroll selection begins
function pickStart() {
    DATA.scrollEnd = false;
}

/ * * *@description: Raises the event */ when the scrolling selection ends
function pickEnd() {
    DATA.scrollEnd = true;
}

/ * * *@description: Click the cancel button */
function cancle() {
    closePicker();
    context.emit("cancleFn");
}

/ * * *@description: Click ok */
function sure() {
    const { scrollEnd, tempValue } = DATA;
    if(! scrollEnd)return;
    const backData = getBackDataFromValue(tempValue);
    closePicker();
    context.emit("sureFn", {
        choosedData: backData,
        choosedIndexArr: tempValue,
    });
}
Copy the code

Data initialization is performed by determining the 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: Gets the index */ by 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);
            returngetIndexByIdOfObject( listData[i].children, idArr, key, arr ); }}}/ * * *@description: Filter data without children */
function getColumnData(arr: Array<object>) {
    return arr.map((v) = > fomateObj(v));
}
function fomateObj(o: any) {
    const temp: any = {};
    for (const k ino) { k ! = ="children" && (temp[k] = o[k]);
    }
    return temp;
}
Copy the code

The change event is triggered when the scrollselection is performed. Determine whether linkage processing is performed according to scrollType

/ * * *@description: Raises the change event */ while scrolling through the selection
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];

        // Set the value association
        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: linkage: checks whether the sub-index is 0 when scrolling. If it is not, returns for processing */
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;
}
/ * * *@descriptionWhen the first column is changed, the subscripts of all child elements are changed to 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;
}
Copy the code

IsPlainObject ()

/ * * *@description: Gets the data type */
function _typeof(obj: any) {
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
/ * * *@description: Checks whether object */ is used
export function isPlainObject(obj: any) {
    return _typeof(obj) === 'object';
}
Copy the code

Use:

Parameters that
name type required default description
isShowPicker Boolean is false Show hidden picker
listData Array is [] The data source
scrollType String no “normal”
cancelText String no “” Cancel button copy
sureText String no “” Confirm button copy
sureStyle String no “” Confirm button style
cancelStyle String no “” Cancel button style
titleText String no “” Headline copy
titleStyle String no “” Heading styles
pickerHeaderStyle String no “” Title bar style
defaultPickData Array no [] Default selection data
keyWordsOfShow String no “name” When each member of the listData is an array of objects, keyWordsOfShow is the object’s key and its value is displayed. Or the key to display when picker=’link’
cancleFn Funciton no “” Click to cancel the triggered event
sureFn Funciton no “” Click ok to trigger the event

Introduce the component in the page

import bottomPop from "@/components/bottomPop/bottomPop.vue"; <bottom-pop :isShowPicker="showPicker2" :listData="listData2" scrollType="normal" cancelText=" cancelText "sureText=" confirm" SureStyle ="color:green" cancelStyle="color:red" titleText=" multiple non-linkage "titleStyle="color:orange" pickerHeaderStyle="height:160rpx;" :defaultPickData="defaultPickData2" keyWordsOfShow="str" @cancleFn="cancleCallBack2" @sureFn="sureCallBack2" ></bottom-pop>Copy the code

Click OK to cancel the event

function sureCallBack2(e: any) {
    console.log("Hit OK");
}
function cancleCallBack2() {
    console.log("Click close");
}
Copy the code