| | |
| | | <view class="uni-stat__select"> |
| | | <span v-if="label" class="uni-label-text hide-on-phone">{{label + ':'}}</span> |
| | | <view class="uni-stat-box" :class="{'uni-stat__actived': current}"> |
| | | <view class="uni-select" :class="{'uni-select--disabled':disabled}"> |
| | | <view class="uni-select__input-box" @click="toggleSelector"> |
| | | <view v-if="current" class="uni-select__input-text">{{textShow}}</view> |
| | | <view v-else class="uni-select__input-text uni-select__input-placeholder">{{typePlaceholder}}</view> |
| | | <view v-if="current && clear && !disabled" @click.stop="clearVal"> |
| | | <view class="uni-select" :class="{'uni-select--disabled':disabled, 'uni-select--wrap': shouldWrap , 'border-default': mode == 'default','border-bottom': mode == 'underline'}"> |
| | | <view class="uni-select__input-box" @click="toggleSelector" :class="{'uni-select__input-box--wrap': shouldWrap}"> |
| | | <view v-if="slotSelected" class="slot-content padding-top-bottom" :class="{'uni-select__input-text--wrap': shouldWrap}"> |
| | | <slot name="selected" :selectedItems="getSelectedItems()"></slot> |
| | | </view> |
| | | <template v-else> |
| | | <view v-if="textShow" class="uni-select__input-text" :class="{'uni-select__input-text--wrap': shouldWrap}"> |
| | | <view class="padding-top-bottom" :class="'align-'+align">{{textShow}}</view> |
| | | </view> |
| | | <view v-else class="uni-select__input-text uni-select__input-placeholder" :class="'align-'+align">{{typePlaceholder}}</view> |
| | | </template> |
| | | <view key="clear-button" v-if="!hideRight && shouldShowClear && clear && !disabled" @click.stop="clearVal"> |
| | | <uni-icons type="clear" color="#c0c4cc" size="24" /> |
| | | </view> |
| | | <view v-else> |
| | | <view key="arrow-button" v-else-if="!hideRight"> |
| | | <uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" /> |
| | | </view> |
| | | </view> |
| | | <view class="uni-select--mask" v-if="showSelector" @click="toggleSelector" /> |
| | | <view class="uni-select__selector" :style="getOffsetByPlacement" v-if="showSelector"> |
| | | <view :class="placement=='bottom'?'uni-popper__arrow_bottom':'uni-popper__arrow_top'"></view> |
| | | <scroll-view scroll-y="true" class="uni-select__selector-scroll"> |
| | | <view class="uni-select__selector-empty" v-if="mixinDatacomResData.length === 0"> |
| | | <text>{{emptyTips}}</text> |
| | | </view> |
| | | <view v-else class="uni-select__selector-item" v-for="(item,index) in mixinDatacomResData" :key="index" |
| | | @click="change(item)"> |
| | | <text :class="{'uni-select__selector__disabled': item.disable}">{{formatItemName(item)}}</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | <view class="uni-select__selector" :style="getOffsetByPlacement" v-if="showSelector"> |
| | | <view :class="placement=='bottom'?'uni-popper__arrow_bottom':'uni-popper__arrow_top'"></view> |
| | | <scroll-view scroll-y="true" class="uni-select__selector-scroll"> |
| | | <template v-if="slotEmpty && mixinDatacomResData.length === 0"> |
| | | <view class="uni-select__selector-empty"> |
| | | <slot name="empty" :empty="emptyTips"></slot> |
| | | </view> |
| | | </template> |
| | | <template v-else> |
| | | <view v-if="mixinDatacomResData.length === 0" class="uni-select__selector-empty"> |
| | | <text>{{emptyTips}}</text> |
| | | </view> |
| | | </template> |
| | | <template v-if="slotOption"> |
| | | <view v-for="(itemData,index) in mixinDatacomResData" :key="index" @click="change(itemData)"> |
| | | <slot name="option" :item="itemData" :itemSelected="multiple? getCurrentValues().includes(itemData.value):getCurrentValues() == itemData.value"></slot> |
| | | </view> |
| | | </template> |
| | | <template v-else> |
| | | <view v-if="!multiple && mixinDatacomResData.length > 0" class="uni-select__selector-item" v-for="(item,index) in mixinDatacomResData" :key="index" |
| | | @click="change(item)"> |
| | | <text :class="{'uni-select__selector__disabled': item.disable}">{{formatItemName(item)}}</text> |
| | | </view> |
| | | <view v-if="multiple && mixinDatacomResData.length > 0" > |
| | | <checkbox-group @change="checkBoxChange"> |
| | | <label class="uni-select__selector-item" v-for="(item,index) in mixinDatacomResData" :key="index" > |
| | | <checkbox :value="index+''" :checked="getCurrentValues().includes(item.value)" :disabled="item.disable"></checkbox> |
| | | <view :class="{'uni-select__selector__disabled': item.disable}">{{formatItemName(item)}}</view> |
| | | </label> |
| | | </checkbox-group> |
| | | </view> |
| | | </template> |
| | | </scroll-view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | * DataChecklist 数据选择器 |
| | | * @description 通过数据渲染的下拉框组件 |
| | | * @tutorial https://uniapp.dcloud.io/component/uniui/uni-data-select |
| | | * @property {String} value 默认值 |
| | | * @property {String|Array} value 默认值,多选时为数组 |
| | | * @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}] |
| | | * @property {Boolean} clear 是否可以清空已选项 |
| | | * @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效 |
| | | * @property {String} label 左侧标题 |
| | | * @property {String} placeholder 输入框的提示文字 |
| | | * @property {Boolean} disabled 是否禁用 |
| | | * @property {Boolean} multiple 是否多选模式 |
| | | * @property {Boolean} wrap 是否允许选中文本换行显示 |
| | | * @property {String} placement 弹出位置 |
| | | * @value top 顶部弹出 |
| | | * @value bottom 底部弹出(default) |
| | | * @property {String} align 选择文字的位置 |
| | | * @value left 显示左侧 |
| | | * @value center 显示中间 |
| | | * @value right 显示 右侧 |
| | | * @property {Boolean} hideRight 是否隐藏右侧按钮 |
| | | * @property {String} mode 边框样式 |
| | | * @value default 四周边框 |
| | | * @value underline 下边框 |
| | | * @value none 无边框 |
| | | * @event {Function} change 选中发生变化触发 |
| | | * @event {Function} open 选择框开启时触发 |
| | | * @event {Function} close 选择框关闭时触发 |
| | | * @event {Function} clear 点击清除按钮之后触发 |
| | | */ |
| | | |
| | | export default { |
| | | name: "uni-data-select", |
| | | mixins: [uniCloud.mixinDatacom || {}], |
| | | emits: [ |
| | | 'open', |
| | | 'close', |
| | | 'update:modelValue', |
| | | 'input', |
| | | 'clear', |
| | | 'change' |
| | | ], |
| | | model: { |
| | | prop: 'modelValue', |
| | | event: 'update:modelValue' |
| | | }, |
| | | options: { |
| | | // #ifdef MP-TOUTIAO |
| | | virtualHost: false, |
| | | // #endif |
| | | // #ifndef MP-TOUTIAO |
| | | virtualHost: true |
| | | // #endif |
| | | }, |
| | | props: { |
| | | localdata: { |
| | | type: Array, |
| | |
| | | } |
| | | }, |
| | | value: { |
| | | type: [String, Number], |
| | | type: [String, Number, Array], |
| | | default: '' |
| | | }, |
| | | modelValue: { |
| | | type: [String, Number], |
| | | type: [String, Number, Array], |
| | | default: '' |
| | | }, |
| | | label: { |
| | |
| | | placement: { |
| | | type: String, |
| | | default: 'bottom' |
| | | } |
| | | }, |
| | | multiple: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | wrap: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | align:{ |
| | | type: String, |
| | | default: "left" |
| | | }, |
| | | hideRight: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | mode:{ |
| | | type: String, |
| | | default: 'default' |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | common |
| | | }, |
| | | valueCom() { |
| | | // #ifdef VUE3 |
| | | return this.modelValue; |
| | | // #endif |
| | | // #ifndef VUE3 |
| | | return this.value; |
| | | // #endif |
| | | if (this.value === '') return this.modelValue |
| | | if (this.modelValue === '') return this.value |
| | | return this.value |
| | | }, |
| | | textShow() { |
| | | // 长文本显示 |
| | | let text = this.current; |
| | | return text; |
| | | if (this.multiple) { |
| | | const currentValues = this.getCurrentValues(); |
| | | if (Array.isArray(currentValues) && currentValues.length > 0) { |
| | | const selectedItems = this.mixinDatacomResData.filter(item => currentValues.includes(item.value)); |
| | | return selectedItems.map(item => this.formatItemName(item)).join(', '); |
| | | } else { |
| | | return ''; // 空数组时返回空字符串,显示占位符 |
| | | } |
| | | } else { |
| | | return this.current; |
| | | } |
| | | }, |
| | | shouldShowClear() { |
| | | if (this.multiple) { |
| | | const currentValues = this.getCurrentValues(); |
| | | return Array.isArray(currentValues) && currentValues.length > 0; |
| | | } else { |
| | | return !!this.current; |
| | | } |
| | | }, |
| | | shouldWrap() { |
| | | // 只有在多选模式、开启换行、且有内容时才应用换行样式 |
| | | return this.multiple && this.wrap && !!this.textShow; |
| | | }, |
| | | getOffsetByPlacement() { |
| | | switch (this.placement) { |
| | |
| | | case 'bottom': |
| | | return "top:calc(100% + 12px);"; |
| | | } |
| | | }, |
| | | slotSelected(){ |
| | | // #ifdef VUE2 |
| | | return this.$scopedSlots ? this.$scopedSlots.selected : false |
| | | // #endif |
| | | // #ifdef VUE3 |
| | | return this.$slots ? this.$slots.selected : false |
| | | // #endif |
| | | }, |
| | | slotEmpty(){ |
| | | // #ifdef VUE2 |
| | | return this.$scopedSlots ? this.$scopedSlots.empty : false |
| | | // #endif |
| | | // #ifdef VUE3 |
| | | return this.$slots ? this.$slots.empty : false |
| | | // #endif |
| | | }, |
| | | slotOption(){ |
| | | // #ifdef VUE2 |
| | | return this.$scopedSlots ? this.$scopedSlots.option : false |
| | | // #endif |
| | | // #ifdef VUE3 |
| | | return this.$slots ? this.$slots.option : false |
| | | // #endif |
| | | } |
| | | }, |
| | | |
| | | watch: { |
| | | showSelector:{ |
| | | handler(val,old){ |
| | | val ? this.$emit('open') : this.$emit('close') |
| | | } |
| | | }, |
| | | localdata: { |
| | | immediate: true, |
| | | handler(val, old) { |
| | |
| | | } |
| | | } |
| | | }, |
| | | |
| | | }, |
| | | methods: { |
| | | getSelectedItems() { |
| | | const currentValues = this.getCurrentValues(); |
| | | let _minxData = this.mixinDatacomResData |
| | | // #ifdef MP-WEIXIN || MP-TOUTIAO |
| | | _minxData = JSON.parse(JSON.stringify(this.mixinDatacomResData)) |
| | | // #endif |
| | | if (this.multiple) { |
| | | return _minxData.filter(item => currentValues.includes(item.value)) || []; |
| | | } else { |
| | | return _minxData.filter(item => item.value === currentValues) || []; |
| | | } |
| | | }, |
| | | debounce(fn, time = 100) { |
| | | let timer = null |
| | | return function(...args) { |
| | |
| | | timer = setTimeout(() => { |
| | | fn.apply(this, args) |
| | | }, time) |
| | | } |
| | | }, |
| | | // 检查项目是否已选中 |
| | | isSelected(item) { |
| | | if (this.multiple) { |
| | | const currentValues = this.getCurrentValues(); |
| | | return Array.isArray(currentValues) && currentValues.includes(item.value); |
| | | } else { |
| | | return this.getCurrentValues() === item.value; |
| | | } |
| | | }, |
| | | // 获取当前选中的值 |
| | | getCurrentValues() { |
| | | if (this.multiple) { |
| | | return Array.isArray(this.valueCom) ? this.valueCom : (this.valueCom ? [this.valueCom] : []); |
| | | } else { |
| | | return this.valueCom; |
| | | } |
| | | }, |
| | | // 执行数据库查询 |
| | |
| | | } |
| | | }, |
| | | initDefVal() { |
| | | let defValue = '' |
| | | let defValue = this.multiple ? [] : '' |
| | | if ((this.valueCom || this.valueCom === 0) && !this.isDisabled(this.valueCom)) { |
| | | defValue = this.valueCom |
| | | } else { |
| | |
| | | if (strogeValue || strogeValue === 0) { |
| | | defValue = strogeValue |
| | | } else { |
| | | let defItem = '' |
| | | let defItem = this.multiple ? [] : '' |
| | | if (this.defItem > 0 && this.defItem <= this.mixinDatacomResData.length) { |
| | | defItem = this.mixinDatacomResData[this.defItem - 1].value |
| | | defItem = this.multiple ? [this.mixinDatacomResData[this.defItem - 1].value] : this.mixinDatacomResData[this.defItem - 1].value |
| | | } |
| | | defValue = defItem |
| | | } |
| | | if (defValue || defValue === 0) { |
| | | if (defValue || defValue === 0 || (this.multiple && Array.isArray(defValue) && defValue.length > 0)) { |
| | | this.emit(defValue) |
| | | } |
| | | } |
| | | const def = this.mixinDatacomResData.find(item => item.value === defValue) |
| | | this.current = def ? this.formatItemName(def) : '' |
| | | |
| | | if (this.multiple) { |
| | | const selectedValues = Array.isArray(defValue) ? defValue : (defValue ? [defValue] : []); |
| | | const selectedItems = this.mixinDatacomResData.filter(item => selectedValues.includes(item.value)); |
| | | this.current = selectedItems.map(item => this.formatItemName(item)); |
| | | } else { |
| | | const def = this.mixinDatacomResData.find(item => item.value === defValue) |
| | | this.current = def ? this.formatItemName(def) : '' |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * @param {[String, Number]} value |
| | | * @param {[String, Number, Array]} value |
| | | * 判断用户给的 value 是否同时为禁用状态 |
| | | */ |
| | | isDisabled(value) { |
| | | let isDisabled = false; |
| | | |
| | | this.mixinDatacomResData.forEach(item => { |
| | | if (item.value === value) { |
| | | isDisabled = item.disable |
| | | } |
| | | }) |
| | | |
| | | return isDisabled; |
| | | if (Array.isArray(value)) { |
| | | // 对于数组,如果任意一个值被禁用,则认为整体被禁用 |
| | | return value.some(val => { |
| | | return this.mixinDatacomResData.some(item => item.value === val && item.disable); |
| | | }); |
| | | } else { |
| | | let isDisabled = false; |
| | | this.mixinDatacomResData.forEach(item => { |
| | | if (item.value === value) { |
| | | isDisabled = item.disable |
| | | } |
| | | }) |
| | | return isDisabled; |
| | | } |
| | | }, |
| | | |
| | | clearVal() { |
| | | this.emit('') |
| | | const emptyValue = this.multiple ? [] : ''; |
| | | this.emit(emptyValue) |
| | | this.current = this.multiple ? [] : '' |
| | | if (this.collection) { |
| | | this.removeCache() |
| | | } |
| | | this.$emit('clear') |
| | | }, |
| | | checkBoxChange(res){ |
| | | let range = res.detail.value |
| | | |
| | | let currentValues = range && range.length > 0? range.map((item)=>{ |
| | | const index = parseInt(item, 10); |
| | | |
| | | if (isNaN(index)) { |
| | | console.error(`无效索引: ${item}`); |
| | | } |
| | | |
| | | if (index < 0 || index >= this.mixinDatacomResData.length) { |
| | | console.error(`索引越界: ${index}`); |
| | | } |
| | | |
| | | return this.mixinDatacomResData[index].value; |
| | | }) : [] |
| | | const selectedItems = this.mixinDatacomResData.filter(dataItem => currentValues.includes(dataItem.value)); |
| | | this.current = selectedItems.map(dataItem => this.formatItemName(dataItem)); |
| | | |
| | | this.emit(currentValues); |
| | | }, |
| | | change(item) { |
| | | if (!item.disable) { |
| | | this.showSelector = false |
| | | this.current = this.formatItemName(item) |
| | | this.emit(item.value) |
| | | if (this.multiple) { |
| | | // 多选模式 |
| | | let currentValues = this.getCurrentValues(); |
| | | if (!Array.isArray(currentValues)) { |
| | | currentValues = currentValues ? [currentValues] : []; |
| | | } |
| | | |
| | | const itemValue = item.value; |
| | | const index = currentValues.indexOf(itemValue); |
| | | |
| | | if (index > -1) { |
| | | currentValues.splice(index, 1); |
| | | } else { |
| | | currentValues.push(itemValue); |
| | | } |
| | | |
| | | const selectedItems = this.mixinDatacomResData.filter(dataItem => currentValues.includes(dataItem.value)); |
| | | this.current = selectedItems.map(dataItem => this.formatItemName(dataItem)); |
| | | |
| | | this.emit(currentValues); |
| | | } else { |
| | | // 单选模式 |
| | | this.showSelector = false |
| | | this.current = this.formatItemName(item) |
| | | this.emit(item.value) |
| | | } |
| | | } |
| | | }, |
| | | emit(val) { |
| | |
| | | $uni-main-color: #333 !default; |
| | | $uni-secondary-color: #909399 !default; |
| | | $uni-border-3: #e5e5e5; |
| | | $uni-primary: #2979ff !default; |
| | | $uni-success: #4cd964 !default; |
| | | $uni-warning: #f0ad4e !default; |
| | | $uni-error: #dd524d !default; |
| | | $uni-info: #909399 !default; |
| | | |
| | | /* #ifndef APP-NVUE */ |
| | | @media screen and (max-width: 500px) { |
| | |
| | | } |
| | | |
| | | .uni-stat-box { |
| | | background-color: #fff; |
| | | width: 100%; |
| | | flex: 1; |
| | | } |
| | |
| | | margin-right: 5px; |
| | | } |
| | | |
| | | .border-bottom { |
| | | border-bottom: solid 1px $uni-border-3; |
| | | } |
| | | |
| | | .border-default { |
| | | border: 1px solid $uni-border-3; |
| | | } |
| | | |
| | | .uni-select { |
| | | font-size: 14px; |
| | | border: 1px solid $uni-border-3; |
| | | box-sizing: border-box; |
| | | border-radius: 4px; |
| | | padding: 0 5px; |
| | |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | align-items: center; |
| | | border-bottom: solid 1px $uni-border-3; |
| | | width: 100%; |
| | | flex: 1; |
| | | height: 35px; |
| | | min-height: 35px; |
| | | |
| | | &--disabled { |
| | | background-color: #f5f7fa; |
| | | cursor: not-allowed; |
| | | } |
| | | |
| | | &--wrap { |
| | | height: auto; |
| | | min-height: 35px; |
| | | // align-items: flex-start; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .uni-select__input-box { |
| | | height: 35px; |
| | | // height: 35px; |
| | | width: 0px; |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | |
| | | flex: 1; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | |
| | | &--wrap { |
| | | .uni-select__input-text { |
| | | margin-right: 8px; |
| | | } |
| | | } |
| | | |
| | | .padding-top-bottom { |
| | | padding-top: 5px; |
| | | padding-bottom: 5px; |
| | | } |
| | | |
| | | .slot-content { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | | } |
| | | } |
| | | |
| | | .uni-select__input { |
| | |
| | | display: flex; |
| | | cursor: pointer; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | align-items: center; |
| | | line-height: 35px; |
| | | font-size: 14px; |
| | | text-align: center; |
| | | /* border-bottom: solid 1px $uni-border-3; */ |
| | | padding: 0px 10px; |
| | | } |
| | | |
| | | .uni-select__selector-item:hover { |
| | | background-color: #f9f9f9; |
| | | |
| | | |
| | | .uni-select__selector-item-check { |
| | | margin-left: auto; |
| | | } |
| | | |
| | | .uni-select__selector-empty:last-child, |
| | |
| | | .uni-popper__arrow_bottom, |
| | | .uni-popper__arrow_bottom::after, |
| | | .uni-popper__arrow_top, |
| | | .uni-popper__arrow_top::after, |
| | | { |
| | | position: absolute; |
| | | display: block; |
| | | width: 0; |
| | | height: 0; |
| | | border-color: transparent; |
| | | border-style: solid; |
| | | border-width: 6px; |
| | | .uni-popper__arrow_top::after { |
| | | position: absolute; |
| | | display: block; |
| | | width: 0; |
| | | height: 0; |
| | | border-color: transparent; |
| | | border-style: solid; |
| | | border-width: 6px; |
| | | } |
| | | |
| | | .uni-popper__arrow_bottom { |
| | |
| | | text-overflow: ellipsis; |
| | | -o-text-overflow: ellipsis; |
| | | overflow: hidden; |
| | | |
| | | &--wrap { |
| | | white-space: normal; |
| | | text-overflow: initial; |
| | | -o-text-overflow: initial; |
| | | overflow: visible; |
| | | word-wrap: break-word; |
| | | word-break: break-all; |
| | | // line-height: 1.5; |
| | | } |
| | | } |
| | | |
| | | .uni-select__input-placeholder { |
| | | color: $uni-base-color; |
| | | font-size: 12px; |
| | | margin: 1px 0; |
| | | } |
| | | |
| | | .uni-select--mask { |
| | |
| | | left: 0; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .align-left { |
| | | text-align: left; |
| | | } |
| | | |
| | | .align-center { |
| | | text-align: center; |
| | | } |
| | | |
| | | .align-right { |
| | | text-align: right; |
| | | } |
| | | |
| | | </style> |