uni-app_单选、多选_搜索框组件

uni-app 单选、多选 搜索框组件

<template>
    <view style="width: 100%">
        <view
            class="search-input-content"
            :style="{
                width: inputWidth ? inputWidth : '100%',
            }"
        >
            <view
                class="c-search-input single-value"
                @click="showSearch(innerValue)"
                v-if="mode === 'single-value'"
                :style="inputStyle"
            >
                <view class="search-icon">
                    
                    <u-icon name="search" :size="32" color="#c0c4cc">u-icon>
                view>
                <span
                    v-if="innerValue === '' || innerValue == null"
                    class="placeholder"
                >
                    {{ placeholder }}
                span>
                <view
                    class="innerValue"
                    v-if="!(innerValue === '' || innerValue == null)"
                >
                    {{ innerValue }}
                view>
            view>
            <view
                class="c-search-input-m mutil-value"
                v-if="mode === 'mutil-value'"
                @click="showSearch"
                :style="inputStyle"
            >
                <span v-if="innerValueList.length === 0" class="placeholder">{{
                    placeholder
                }}span>
                <block v-for="(item, index) of innerValueList" :key="index">
                    <u-tag
                        :text="item"
                        shape="square"
                        :closeable="!disabled"
                        type="warning"
                        @close="close(index)"
                        class="mutil-tag"
                    />
                block>
            view>
            <view
                class="close"
                @click="clear"
                v-show="
                    clearable &&
                        (innerValue || innerValueList.length > 0) &&
                        !disabled
                "
            >
                <u-icon
                    name="close-circle-fill"
                    size="32"
                    color="#c0c4cc"
                >u-icon>
            view>
        view>
        <u-popup v-model="show" mode="top">
            <view class="search-popup">
                <view class="search-popup-input">
                    <u-icon
                        class="u-clear-icon"
                        :size="32"
                        name="search"
                    >u-icon>
                    <input
                        class="inner-search-input"
                        v-model="search[searchParam]"
                        placeholder="Enter search keywords"
                        :focus="focusState"
                        @input="getListByKeyword_debounce"
                        @blur="focusState = false"
                    />
                view>
                <picker-view
                    @change="bindChange"
                    class="picker-view"
                    :style="{
                        height: `${pickerViewHeight}rpx`,
                    }"
                    :indicator-style="indicatorStyle"
                >
                    <picker-view-column>
                        <view
                            class="item"
                            v-for="(item, index) in list"
                            :key="index"
                            >{{ renderLabel(item) }}view
                        >
                    picker-view-column>
                picker-view>
                <view class="search-action">
                    <span @click="show = false">{{
                        mode === 'mutil-value' ? 'Back' : 'Cancel'
                    }}span>
                    <span
                        v-if="mode === 'single-value'"
                        class="action"
                        @click="ok"
                        >OKspan
                    >
                    <span v-if="mode === 'mutil-value'" class="action">
                        <span @click="add">Addspan>
                    span>
                view>
                <view
                    class="c-search-input-m mutil-value"
                    v-if="mode === 'mutil-value'"
                    ><span v-if="innerValueList.length === 0">
                        {{ placeholder }}
                    span>
                    <block v-for="(item, index) of innerValueList" :key="index">
                        <u-tag
                            :text="item"
                            shape="square"
                            :closeable="!disabled"
                            type="warning"
                            @close="close(index)"
                            class="mutil-tag"
                        />
                    block>
                view>
            view>
        u-popup>
        <u-toast ref="uToast" />
    view>
template>
<script lang="ts">
import { defineComponent, reactive, PropType } from '@vue/composition-api'
import { SearchCondition } from '@/common/model'
export default defineComponent({
    props: {
        // v-model 绑定值
        value: {
            default: '',
            required: true,
        },
        // 用于初始化内部显示值
        initInnerValue: {
            type: String,
            default: '',
        },
        // 用于显示api返回list的label值
        label: {
            type: String,
            default: 'label',
        },
        // 用于显示api返回list的label值
        labelArr: {
            type: Array,
            default: () => [],
        },
        // 用于显示api返回list的value值
        valueName: {
            type: String,
            default: 'value',
        },
        // 用于判断单选或多选
        mode: {
            type: String,
            default: 'single-value',
            // single-value 单选
            // mutil-value 多选
        },
        // placeholder
        placeholder: {
            type: String,
            default: 'click the input box to enter',
        },
        // search 查询 api
        api: {
            type: Function as PropType<
                (searchCondition: SearchCondition) => Promise<any>
            >,
            default: () => {},
        },
        // search 查询 api
        url: {
            type: String,
        },
        // 可以直接传入已存在的查询列表,使用后将不采用api
        searchList: {
            type: Array,
            default: () => [],
        },
        // 是否可以清除
        clearable: {
            type: Boolean,
            default: true,
        },
        // 设置输入框宽度
        inputWidth: {
            type: String,
        },
        border: {
            type: Boolean,
            default: true,
        },
        // 输入框圆角率 默认 10rpx
        radius: {
            type: String,
            default: '10',
        },
        height: {
            type: String,
            default: '70',
        },
        pickerViewHeight: {
            type: String,
            default: '250',
        },
        // 禁止输入
        disabled: {
            type: Boolean,
            default: false,
        },
        searchParam: {
            type: String,
            default: 'keyword',
        },
        additionalConditions: {
            type: Object,
            default: () => {},
        },
    },
    created() {
        this.DB = this._.debounce(this.getListByKeyword, 300, true)
        this.DB()
    },
    mounted() {
        this.$nextTick(() => {
            if (this.initInnerValue !== '') {
                this.changeInnerValue()
            }
        })
    },
    watch: {
        initInnerValue() {
            this.changeInnerValue()
        },
    },
    computed: {
        inputStyle: function(): object {
            return {
                'border-radius': `${this.radius}rpx`,
                'line-height': `${this.height}rpx`,
                'min-height': `${this.height}rpx`,
                'box-shadow':
                    this.border && !this.disabled
                        ? `0 0 0 2rpx #e8e8e8`
                        : `none`,
            }
        },
    },
    methods: {
        renderLabel(item: any) {
            if (this.labelArr.length > 0) {
                let str = ''
                this.labelArr.forEach((el: any, i: number) => {
                    str = str + item[el]
                    if (i < this.labelArr.length - 1) {
                        str = str + '  |  '
                    }
                })
                return str
            } else {
                return item[this.label]
            }
        },
        changeInnerValue() {
            this.innerValue = this.initInnerValue
        },
        bindChange: function(e: any) {
            this.searchIndex = e.detail.value[0]
            let count = this.search.pageIndex * this.search.pageSize
            if (
                this.searchIndex === count - 1 &&
                this.searchIndex < this.search.total - 1
            ) {
                this.search.pageIndex++
                this.DB(true)
            }
        },
        getListByKeyword_debounce() {
            this.DB()
        },
        showSearch(innerValue?: string) {
            if (!this.disabled) {
                this.show = true
            } else {
                if (innerValue) {
                    ;(this.$refs.uToast as any).show({
                        title: innerValue,
                        icon: false,
                    })
                }
            }
        },
        getListByKeyword(isScroll?: boolean) {
            if (this.searchList.length > 0) {
                let filter = this.searchList.filter((f) => {
                    return (
                        ((f as any)[this.label] as String)
                            .toLowerCase()
                            .replace(/\s/g, '')
                            .match(
                                (this.search as any)[
                                    this.searchParam
                                ].toLowerCase()
                            ) !== null
                    )
                })
                this.list.length = 0
                this.list.push(...filter)
            } else {
                if (!isScroll) {
                    this.search.pageIndex = 1
                }
                this.search = Object.assign(
                    this.search,
                    this.additionalConditions
                ) as any
                if (this.url) {
                    this.$request
                        .get(this.url, this.search)
                        .then((data: any) => {
                            if (data.Result == 1 && data.Data) {
                                if (!isScroll) {
                                    this.list.length = 0
                                }
                                if (
                                    JSON.parse(
                                        JSON.stringify(data.Data)
                                    ) instanceof Array
                                ) {
                                    this.list.push(...data.Data)
                                    this.search.total = data.Data.Total
                                } else if (
                                    JSON.parse(
                                        JSON.stringify(data.Data.DataList)
                                    ) instanceof Array
                                ) {
                                    this.list.push(...data.Data.DataList)
                                    this.search.total = data.Data.Total
                                }
                            }
                        })
                } else {
                    ;(this.api as Function)(this.search).then((data: any) => {
                        if (data.Result == 1 && data.Data) {
                            if (!isScroll) {
                                this.list.length = 0
                            }
                            if (
                                JSON.parse(JSON.stringify(data.Data)) instanceof
                                Array
                            ) {
                                this.list.push(...data.Data)
                                this.search.total = data.Data.Total
                            } else if (
                                JSON.parse(
                                    JSON.stringify(data.Data.DataList)
                                ) instanceof Array
                            ) {
                                this.list.push(...data.Data.DataList)
                                this.search.total = data.Data.Total
                            }
                        }
                    })
                }
            }
        },
        ok() {
            let list = this.list
            if (list.length > 0) {
                let item = list[this.searchIndex]
                this.actionItem = item
                this.innerValue = item[this.label]
                ;(this.search as any)[this.searchParam] = ''
                this.$emit('input', item[this.valueName])
                this.$emit('ok', item)
                this.show = false
            }
        },
        add() {
            let list = this.list
            if (list.length > 0) {
                let item = list[this.searchIndex]
                if (!this.tempValueList.includes(item[this.valueName])) {
                    this.innerValueList.push(item[this.label])
                    this.tempValueList.push(item[this.valueName])
                    this.$emit('input', this.tempValueList)
                }
            }
            // #ifdef H5
            // (this.$refs['inner-search-input'] as any)._focus()
            // #endif
            this.$nextTick(() => {
                this.focusState = true
            })
        },
        close(index: number) {
            this.innerValueList.splice(index, 1)
            this.tempValueList.splice(index, 1)
            this.$emit('input', this.tempValueList)
        },
        clear() {
            if (this.mode === 'single-value') {
                this.innerValue = ''
                this.$emit('input', '')
            }
            if (this.mode === 'mutil-value') {
                this.innerValueList = []
                this.tempValueList = []
                this.$emit('input', this.tempValueList)
            }
            this.$emit('clear')
        },
    },
    setup() {
        let data = reactive({
            show: false,
            focusState: true,
            list: [] as any[],
            total: 0,
            actionItem: {},
            search: new SearchCondition(),
            innerValue: '',
            innerValueList: [] as any[],
            searchIndex: 0,
            tempValueList: [] as any[],
            indicatorStyle: `height: 70rpx`,
            focus: false,
            DB: (isScroll?: boolean) => {},
        })
        return data
    },
})
script>
<style lang="scss" scoped>
.search-input-content {
    width: 100%;
    display: flex;
    position: relative;
    .c-search-input {
        display: flex;
        width: 0;
        flex: 1;
        min-height: 70rpx;
        border-radius: 8rpx;
        line-height: 70rpx;
        padding-left: 20rpx;
        > span {
            color: #ccc;
        }
        .innerValue {
            margin-left: 5rpx;
            width: 84%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
    }
    .c-search-input-m {
        width: 100%;
        min-height: 70rpx;
        border-radius: 8rpx;
        line-height: 70rpx;
        padding-left: 10rpx;
    }
    .mutil-value {
        height: auto;
    }
    .search-icon {
        margin-right: 6rpx;
    }
    .close {
        line-height: 28rpx;
        height: 28rpx;
        position: absolute;
        margin-top: -14rpx;
        top: 50%;
        right: 15rpx;
    }
}
.mutil-tag {
    margin-right: 10rpx;
}
.search-popup {
    padding: 20rpx 35rpx 10rpx 35rpx;
    .search-action {
        padding-bottom: 10rpx;
        display: flex;
        justify-content: space-between;
        .action {
            color: $laxton-type-active;
        }
    }
    .search-popup-input {
        display: flex;
        justify-content: space-between;
        margin-top: 20rpx;
        > input {
            flex: 1;
            height: 70rpx;
            border-radius: 30rpx;
            padding-left: 15rpx;
            margin-left: 30rpx;
            line-height: 70rpx;
            background-color: #e2e2e2;
        }
    }
    .picker-view {
        margin-top: 20rpx;
    }
    .item {
        align-items: center;
        justify-content: center;
        text-align: center;
        line-height: 70rpx;
    }
}
style>

你可能感兴趣的:(uni-app,vue,javascript)