552 lines
14 KiB
Vue
552 lines
14 KiB
Vue
<template>
|
||
<view class="uni-data-tree">
|
||
<view class="uni-data-tree-input" @click="handleInput">
|
||
<slot :options="options" :data="inputSelected" :error="errorMessage">
|
||
<view class="input-value" :class="{'input-value-border': border}">
|
||
<text v-if="errorMessage" class="selected-area error-text">{{errorMessage}}</text>
|
||
<view v-else-if="loading && !isOpened" class="selected-area">
|
||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||
</view>
|
||
<scroll-view v-else-if="inputSelected.length" class="selected-area" scroll-x="true">
|
||
<view class="selected-list">
|
||
<view class="selected-item" v-for="(item,index) in inputSelected" :key="index">
|
||
<text class="text-color">{{item.text}}</text><text v-if="index<inputSelected.length-1"
|
||
class="input-split-line">{{split}}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<text v-else class="selected-area placeholder">{{placeholder}}</text>
|
||
<view v-if="clearIcon && !readonly && inputSelected.length" class="icon-clear" @click.stop="clear">
|
||
<uni-icons type="clear" color="#c0c4cc" size="24"></uni-icons>
|
||
</view>
|
||
<view class="arrow-area" v-if="(!clearIcon || !inputSelected.length) && !readonly ">
|
||
<view class="input-arrow"></view>
|
||
</view>
|
||
</view>
|
||
</slot>
|
||
</view>
|
||
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
|
||
<view class="uni-data-tree-dialog" v-if="isOpened">
|
||
<view class="uni-popper__arrow"></view>
|
||
<view class="dialog-caption">
|
||
<view class="title-area">
|
||
<text class="dialog-title">{{popupTitle}}</text>
|
||
</view>
|
||
<view class="dialog-close" @click="handleClose">
|
||
<view class="dialog-close-plus" data-id="close"></view>
|
||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||
</view>
|
||
</view>
|
||
<data-picker-view class="picker-view" ref="pickerView" v-model="dataValue" :localdata="localdata"
|
||
:preload="preload" :collection="collection" :field="field" :orderby="orderby" :where="where"
|
||
:step-searh="stepSearh" :self-field="selfField" :parent-field="parentField" :managed-mode="true" :map="map"
|
||
:ellipsis="ellipsis" @change="onchange" @datachange="ondatachange" @nodeclick="onnodeclick">
|
||
</data-picker-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import dataPicker from "../uni-data-pickerview/uni-data-picker.js"
|
||
import DataPickerView from "../uni-data-pickerview/uni-data-pickerview.vue"
|
||
|
||
/**
|
||
* DataPicker 级联选择
|
||
* @description 支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
|
||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||
* @property {String} popup-title 弹出窗口标题
|
||
* @property {Array} localdata 本地数据,参考
|
||
* @property {Boolean} border = [true|false] 是否有边框
|
||
* @property {Boolean} readonly = [true|false] 是否仅读
|
||
* @property {Boolean} preload = [true|false] 是否预加载数据
|
||
* @value true 开启预加载数据,点击弹出窗口后显示已加载数据
|
||
* @value false 关闭预加载数据,点击弹出窗口后开始加载数据
|
||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||
* @value true 启用分布查询,仅查询当前选中节点
|
||
* @value false 关闭分布查询,一次查询出所有数据
|
||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||
* @property {String|DBCollectionString} collection 表名
|
||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||
* @property {String} orderby 排序字段及正序倒叙设置
|
||
* @property {String|JQLString} where 查询条件
|
||
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
|
||
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
|
||
*/
|
||
export default {
|
||
name: 'UniDataPicker',
|
||
emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue','inputclick'],
|
||
mixins: [dataPicker],
|
||
components: {
|
||
DataPickerView
|
||
},
|
||
props: {
|
||
options: {
|
||
type: [Object, Array],
|
||
default () {
|
||
return {}
|
||
}
|
||
},
|
||
popupTitle: {
|
||
type: String,
|
||
default: '请选择'
|
||
},
|
||
placeholder: {
|
||
type: String,
|
||
default: '请选择'
|
||
},
|
||
heightMobile: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
readonly: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
clearIcon: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
border: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
split: {
|
||
type: String,
|
||
default: '/'
|
||
},
|
||
ellipsis: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
isOpened: false,
|
||
inputSelected: []
|
||
}
|
||
},
|
||
created() {
|
||
this.$nextTick(() => {
|
||
this.load();
|
||
})
|
||
},
|
||
watch: {
|
||
localdata: {
|
||
handler() {
|
||
this.load()
|
||
},
|
||
deep: true
|
||
},
|
||
},
|
||
methods: {
|
||
clear() {
|
||
this._dispatchEvent([]);
|
||
},
|
||
onPropsChange() {
|
||
this._treeData = [];
|
||
this.selectedIndex = 0;
|
||
|
||
this.load();
|
||
},
|
||
load() {
|
||
if (this.readonly) {
|
||
this._processReadonly(this.localdata, this.dataValue);
|
||
return;
|
||
}
|
||
|
||
// 回显本地数据
|
||
if (this.isLocalData) {
|
||
this.loadData();
|
||
this.inputSelected = this.selected.slice(0);
|
||
} else if (this.isCloudDataList || this.isCloudDataTree) { // 回显 Cloud 数据
|
||
this.loading = true;
|
||
this.getCloudDataValue().then((res) => {
|
||
this.loading = false;
|
||
this.inputSelected = res;
|
||
}).catch((err) => {
|
||
this.loading = false;
|
||
this.errorMessage = err;
|
||
})
|
||
}
|
||
},
|
||
show() {
|
||
this.isOpened = true
|
||
setTimeout(() => {
|
||
this.$refs.pickerView.updateData({
|
||
treeData: this._treeData,
|
||
selected: this.selected,
|
||
selectedIndex: this.selectedIndex
|
||
})
|
||
}, 200)
|
||
this.$emit('popupopened')
|
||
},
|
||
hide() {
|
||
this.isOpened = false
|
||
this.$emit('popupclosed')
|
||
},
|
||
handleInput() {
|
||
if (this.readonly) {
|
||
this.$emit('inputclick')
|
||
return
|
||
}
|
||
this.show()
|
||
},
|
||
handleClose(e) {
|
||
this.hide()
|
||
},
|
||
onnodeclick(e) {
|
||
this.$emit('nodeclick', e)
|
||
},
|
||
ondatachange(e) {
|
||
this._treeData = this.$refs.pickerView._treeData
|
||
},
|
||
onchange(e) {
|
||
this.hide()
|
||
this.$nextTick(() => {
|
||
this.inputSelected = e;
|
||
})
|
||
this._dispatchEvent(e)
|
||
},
|
||
_processReadonly(dataList, value) {
|
||
var isTree = dataList.findIndex((item) => {
|
||
return item.children
|
||
})
|
||
if (isTree > -1) {
|
||
let inputValue
|
||
if (Array.isArray(value)) {
|
||
inputValue = value[value.length - 1]
|
||
if (typeof inputValue === 'object' && inputValue.value) {
|
||
inputValue = inputValue.value
|
||
}
|
||
} else {
|
||
inputValue = value
|
||
}
|
||
this.inputSelected = this._findNodePath(inputValue, this.localdata)
|
||
return
|
||
}
|
||
|
||
if (!this.hasValue) {
|
||
this.inputSelected = []
|
||
return
|
||
}
|
||
|
||
let result = []
|
||
for (let i = 0; i < value.length; i++) {
|
||
var val = value[i]
|
||
var item = dataList.find((v) => {
|
||
return v.value == val
|
||
})
|
||
if (item) {
|
||
result.push(item)
|
||
}
|
||
}
|
||
if (result.length) {
|
||
this.inputSelected = result
|
||
}
|
||
},
|
||
_filterForArray(data, valueArray) {
|
||
var result = []
|
||
for (let i = 0; i < valueArray.length; i++) {
|
||
var value = valueArray[i]
|
||
var found = data.find((item) => {
|
||
return item.value == value
|
||
})
|
||
if (found) {
|
||
result.push(found)
|
||
}
|
||
}
|
||
return result
|
||
},
|
||
_dispatchEvent(selected) {
|
||
let item = {}
|
||
if (selected.length) {
|
||
var value = new Array(selected.length)
|
||
for (var i = 0; i < selected.length; i++) {
|
||
value[i] = selected[i].value
|
||
}
|
||
item = selected[selected.length - 1]
|
||
} else {
|
||
item.value = ''
|
||
}
|
||
if (this.formItem) {
|
||
this.formItem.setValue(item.value)
|
||
}
|
||
|
||
this.$emit('input', item.value)
|
||
this.$emit('update:modelValue', item.value)
|
||
this.$emit('change', {
|
||
detail: {
|
||
value: selected
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.uni-data-tree {
|
||
flex: 1;
|
||
position: relative;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.error-text {
|
||
color: #DD524D;
|
||
}
|
||
|
||
.input-value {
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
font-size: 14px;
|
||
/* line-height: 35px; */
|
||
padding: 0 10px;
|
||
padding-right: 5px;
|
||
overflow: hidden;
|
||
height: 35px;
|
||
/* #ifndef APP-NVUE */
|
||
box-sizing: border-box;
|
||
/* #endif */
|
||
}
|
||
|
||
.input-value-border {
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.selected-area {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
}
|
||
|
||
.load-more {
|
||
/* #ifndef APP-NVUE */
|
||
margin-right: auto;
|
||
/* #endif */
|
||
/* #ifdef APP-NVUE */
|
||
width: 40px;
|
||
/* #endif */
|
||
}
|
||
|
||
.selected-list {
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
/* padding: 0 5px; */
|
||
}
|
||
|
||
.selected-item {
|
||
flex-direction: row;
|
||
/* padding: 0 1px; */
|
||
/* #ifndef APP-NVUE */
|
||
white-space: nowrap;
|
||
/* #endif */
|
||
}
|
||
|
||
.text-color {
|
||
color: #333;
|
||
}
|
||
|
||
.placeholder {
|
||
color: grey;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.input-split-line {
|
||
opacity: .5;
|
||
}
|
||
|
||
.arrow-area {
|
||
position: relative;
|
||
width: 20px;
|
||
/* #ifndef APP-NVUE */
|
||
margin-bottom: 5px;
|
||
margin-left: auto;
|
||
display: flex;
|
||
/* #endif */
|
||
justify-content: center;
|
||
transform: rotate(-45deg);
|
||
transform-origin: center;
|
||
}
|
||
|
||
.input-arrow {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-left: 1px solid #999;
|
||
border-bottom: 1px solid #999;
|
||
}
|
||
|
||
.uni-data-tree-cover {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, .4);
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: column;
|
||
z-index: 100;
|
||
}
|
||
|
||
.uni-data-tree-dialog {
|
||
position: fixed;
|
||
left: 0;
|
||
/* #ifndef APP-NVUE */
|
||
top: 20%;
|
||
/* #endif */
|
||
/* #ifdef APP-NVUE */
|
||
top: 200px;
|
||
/* #endif */
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: #FFFFFF;
|
||
border-top-left-radius: 10px;
|
||
border-top-right-radius: 10px;
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: column;
|
||
z-index: 102;
|
||
overflow: hidden;
|
||
/* #ifdef APP-NVUE */
|
||
width: 750rpx;
|
||
/* #endif */
|
||
}
|
||
|
||
.dialog-caption {
|
||
position: relative;
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
/* border-bottom: 1px solid #f0f0f0; */
|
||
}
|
||
|
||
.title-area {
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
align-items: center;
|
||
/* #ifndef APP-NVUE */
|
||
margin: auto;
|
||
/* #endif */
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.dialog-title {
|
||
/* font-weight: bold; */
|
||
line-height: 44px;
|
||
}
|
||
|
||
.dialog-close {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
/* #ifndef APP-NVUE */
|
||
display: flex;
|
||
/* #endif */
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
.dialog-close-plus {
|
||
width: 16px;
|
||
height: 2px;
|
||
background-color: #666;
|
||
border-radius: 2px;
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.dialog-close-rotate {
|
||
position: absolute;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
.picker-view {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.icon-clear {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/* #ifdef H5 */
|
||
@media all and (min-width: 768px) {
|
||
.uni-data-tree-cover {
|
||
background-color: transparent;
|
||
}
|
||
|
||
.uni-data-tree-dialog {
|
||
position: absolute;
|
||
top: 55px;
|
||
height: auto;
|
||
min-height: 400px;
|
||
max-height: 50vh;
|
||
background-color: #fff;
|
||
border: 1px solid #EBEEF5;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
border-radius: 4px;
|
||
overflow: unset;
|
||
}
|
||
|
||
.dialog-caption {
|
||
display: none;
|
||
}
|
||
|
||
.icon-clear {
|
||
/* margin-right: 5px; */
|
||
}
|
||
}
|
||
|
||
/* #endif */
|
||
|
||
/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
|
||
/* #ifndef APP-NVUE */
|
||
.uni-popper__arrow,
|
||
.uni-popper__arrow::after {
|
||
position: absolute;
|
||
display: block;
|
||
width: 0;
|
||
height: 0;
|
||
border-color: transparent;
|
||
border-style: solid;
|
||
border-width: 6px;
|
||
}
|
||
|
||
.uni-popper__arrow {
|
||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||
top: -6px;
|
||
left: 10%;
|
||
margin-right: 3px;
|
||
border-top-width: 0;
|
||
border-bottom-color: #EBEEF5;
|
||
}
|
||
|
||
.uni-popper__arrow::after {
|
||
content: " ";
|
||
top: 1px;
|
||
margin-left: -6px;
|
||
border-top-width: 0;
|
||
border-bottom-color: #fff;
|
||
}
|
||
|
||
/* #endif */
|
||
</style>
|