uniapp 改写uni-data-picker级联选择器(带搜索和多选)新增列出选择和删除功能

5 篇文章 1 订阅
3 篇文章 0 订阅

效果演示

注意:视频中的数据是模拟数据,请自行定义数据才可使用,数据格式末尾有例子

uniapp 改写uni-data-picker

uni-data-picker.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>{{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="#e1e1e1" size="14"></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" :showSearch="showSearch" :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'],
		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
			},
			showSearch: {
				type: Boolean,
				default: false
			},
		},
		data() {
			return {
				isOpened: false,
				inputSelected: []
			}
		},
		created() {
			this.form = this.getForm('uniForms')
			this.formItem = this.getForm('uniFormsItem')
			if (this.formItem) {
				if (this.formItem.name) {
					this.rename = this.formItem.name
					this.form.inputChildrens.push(this)
				}
			}

			this.$nextTick(() => {
				this.load()
			})
		},
		methods: {
			clear() {
				this.inputSelected.splice(0)
				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.parentField && !this.selfField && this.hasValue) {
					this.getNodeData(() => {
						this.inputSelected = this.selected.slice(0)
					})
				} else if (this.hasValue) {
					this.getTreePath(() => {
						this.inputSelected = this.selected.slice(0)
					})
				}
			},
			getForm(name = 'uniForms') {
				let parent = this.$parent;
				let parentName = parent.$options.name;
				while (parentName !== name) {
					parent = parent.$parent;
					if (!parent) return false;
					parentName = parent.$options.name;
				}
				return parent;
			},
			show() {
				this.isOpened = true
				this.$nextTick(() => {
					this.$refs.pickerView.updateData({
						treeData: this._treeData,
						selected: this.selected,
						selectedIndex: this.selectedIndex
					})
				})
				this.$emit('popupopened')
			},
			hide() {
				this.isOpened = false
				this.$emit('popupclosed')
			},
			handleInput() {
				if (this.readonly) {
					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.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 {
		position: relative;
		font-size: 14px;
		min-width:150px;
		max-width:220px;
	}

	.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: 38px;
		padding: 0 5px;
		overflow: hidden;
		/* #ifdef APP-NVUE */
		height: 40px;
		/* #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 */
	}

	.placeholder {
		color: grey;
	}

	.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;
		top: 20%;
		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;
	}

	/* #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>

uni-data-pickerview.vue更改

<template>
	<view class="uni-data-pickerview">
		<uni-combox v-if="showSearch" :candidates="candidates" placeholder="输入搜索内容"  @input="handleInput"></uni-combox>
		<!-- <input placeholder="请输入搜索内容" style="border:1px solid #ddd;width:80%;margin:0 auto;border-radius:5px;padding:5px;" @input="handleInput"/> -->
		<scroll-view class="selected-area" scroll-x="true" scroll-y="false" :show-scrollbar="false">
			<view class="selected-list">
				<template v-for="(item,index) in selected">
					<view class="selected-item"
						:class="{'selected-item-active':index==selectedIndex, 'selected-item-text-overflow': ellipsis}"
						:key="index" v-if="item.text" @click="handleSelect(index)">
						<text class="">{{item.text}}</text>
					</view>
				</template>
			</view>
		</scroll-view>
		<view class="tab-c">
			<template v-for="(child, i) in dataList">
				<scroll-view class="list" :key="i" v-if="i==selectedIndex" :scroll-y="true">
					<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in child" :key="j"
						>
						<checkbox  @click ="handleClick(item)"  v-if="showSearch && i==0" :checked=" selected.length > j && item[map.value] == selected[j].value"/>
						<text @click="handleNodeClick(item, i, j)" class="item-text item-text-overflow">{{item[map.text]}}</text>
						<view class="check" v-if="i == 1 && selected.length > i && item[map.value] == selected[i].value"></view>
					</view>
				</scroll-view>
			</template>

			<view class="loading-cover" v-if="loading">
				<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
			</view>
			<view class="error-message" v-if="errorMessage">
				<text class="error-text">{{errorMessage}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	import dataPicker from "./uni-data-picker.js"

	/**
	 * DataPickerview
	 * @description uni-data-pickerview
	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3796
	 * @property {Array} localdata 本地数据,参考
	 * @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 查询条件
	 */
	export default {
		name: 'UniDataPickerView',
		emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
		mixins: [dataPicker],
		props: {
			managedMode: {
				type: Boolean,
				default: false
			},
			ellipsis: {
				type: Boolean,
				default: true
			},
			showSearch: {
				type: Boolean,
				default: false
			},
		},
		data() {
			return {
				candidates:[],
				nodeList:[],
			}
		},
		created() {
			if (this.managedMode) {
				return
			}
			this.$nextTick(() => {
				this.load()
			})
		},
		methods: {
			//搜索框选择员工
			handleInput(e){
				if(this.candidates.indexOf(e) != -1){
					let index = this.candidates.indexOf(e);
					let node = this.nodeList[index];
					console.log(this.candidates.indexOf(e));
					console.log(node);
					this._dispatchEvent()
					this.$emit('nodeclick', node)
				}
				//console.log(this.loadData());
			},
			//点击选择组别
			handleClick(item){
				let text = item.text;
				for(let i in this.nodeList){
					if(this.nodeList[i].parent_value == text){
						this.$emit('nodeclick', this.nodeList[i])
					}
				}
				this._dispatchEvent()
			},
			onPropsChange() {
				this._treeData = []
				this.selectedIndex = 0
				this.load()
			},
			load() {
				if (this.isLocaldata) {
					this.loadData()
				} else if (this.dataValue.length) {
					this.getTreePath((res) => {
						this.loadData()
					})
				}
			},
			handleSelect(index) {
				this.selectedIndex = index
			},
			handleNodeClick(item, i, j) {
				
				if (item.disable) {
					return
				}
				const node = this.dataList[i][j]
				const text = node[this.map.text]
				const value = node[this.map.value]
				if (i < this.selected.length - 1) {
					this.selected.splice(i, this.selected.length - i)
					this.selected.push({
						text,
						value
					})
					console.log(this.selected);
				} else if (i === this.selected.length - 1) {
					this.selected.splice(i, 1, {
						text,
						value
					})
					console.log(this.selected);
				}

				if (node.isleaf) {
					this.onSelectedChange(node, node.isleaf)
					return
				}
				const {
					isleaf,
					hasNodes
				} = this._updateBindData()

				if (!this._isTreeView() && !hasNodes) {
					this.onSelectedChange(node, true)
					return
				}

				if (this.isLocaldata && (!hasNodes || isleaf)) {
					this.onSelectedChange(node, true)
					return
				}

				if (!isleaf && !hasNodes) {
					this._loadNodeData((data) => {
						if (!data.length) {
							node.isleaf = true
						} else {
							this._treeData.push(...data)
							this._updateBindData(node)
						}
						this.onSelectedChange(node, node.isleaf)
					}, this._nodeWhere())
					return
				}

				this.onSelectedChange(node, false)
			},
			updateData(data) {
				//新增筛选用户功能ld
				let curTreeData=data.treeData;
				this.candidates = [];
				this.nodeList =[];
				for(let i in curTreeData){
					if(curTreeData[i].parent_value){
						this.candidates.push(curTreeData[i].text);
						this.nodeList.push(curTreeData[i])
					}
				}
				this._treeData = data.treeData
				this.selected = data.selected
				if (!this._treeData.length) {
					this.loadData()
				} else {
					//this.selected = data.selected
					this._updateBindData()
				}
			},
			onDataChange() {
				this.$emit('datachange')
			},
			onSelectedChange(node, isleaf) {
				if (isleaf) {
					this._dispatchEvent()
				}

				if (node) {
					this.$emit('nodeclick', node)
				}
			},
			_dispatchEvent() {
				this.$emit('change', this.selected.slice(0))
			}
		}
	}
</script>
<style >
	.uni-data-pickerview {
		flex: 1;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: column;
		overflow: hidden;
		height: 100%;
	}

	.error-text {
		color: #DD524D;
	}

	.loading-cover {
		position: absolute;
		left: 0;
		top: 0;
		right: 0;
		bottom: 0;
		background-color: rgba(255, 255, 255, .5);
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: column;
		align-items: center;
		z-index: 1001;
	}

	.load-more {
		/* #ifndef APP-NVUE */
		margin: auto;
		/* #endif */
	}

	.error-message {
		background-color: #fff;
		position: absolute;
		left: 0;
		top: 0;
		right: 0;
		bottom: 0;
		padding: 15px;
		opacity: .9;
		z-index: 102;
	}

	/* #ifdef APP-NVUE */
	.selected-area {
		width: 750rpx;
	}

	/* #endif */

	.selected-list {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: row;
		flex-wrap: nowrap;
		padding: 0 5px;
		border-bottom: 1px solid #f8f8f8;
	}

	.selected-item {
		margin-left: 10px;
		margin-right: 10px;
		padding: 12px 0;
		text-align: center;
		/* #ifndef APP-NVUE */
		white-space: nowrap;
		/* #endif */
	}

	.selected-item-text-overflow {
		width: 168px;
		/* fix nvue */
		overflow: hidden;
		/* #ifndef APP-NVUE */
		width: 6em;
		white-space: nowrap;
		text-overflow: ellipsis;
		-o-text-overflow: ellipsis;
		/* #endif */
	}

	.selected-item-active {
		border-bottom: 2px solid #007aff;
	}

	.selected-item-text {
		color: #007aff;
	}

	.tab-c {
		position: relative;
		flex: 1;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: row;
		overflow: hidden;
	}

	.list {
		flex: 1;
	}

	.item {
		padding: 12px 15px;
		/* border-bottom: 1px solid #f0f0f0; */
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: row;
		justify-content: space-between;
	}

	.is-disabled {
		opacity: .5;
	}

	.item-text {
		/* flex: 1; */
		color: #333333;
	}

	.item-text-overflow {
		width: 280px;
		/* fix nvue */
		overflow: hidden;
		/* #ifndef APP-NVUE */
		width: 20em;
		white-space: nowrap;
		text-overflow: ellipsis;
		-o-text-overflow: ellipsis;
		/* #endif */
	}

	.check {
		margin-right: 5px;
		border: 2px solid #007aff;
		border-left: 0;
		border-top: 0;
		height: 12px;
		width: 6px;
		transform-origin: center;
		/* #ifndef APP-NVUE */
		transition: all 0.3s;
		/* #endif */
		transform: rotate(45deg);
	}
</style>

uni-data-picker.js修改

export default {
  props: {
    localdata: {
      type: [Array, Object],
      default () {
        return []
      }
    },
    spaceInfo: {
      type: Object,
      default () {
        return {}
      }
    },
    collection: {
      type: String,
      default: ''
    },
    action: {
      type: String,
      default: ''
    },
    field: {
      type: String,
      default: ''
    },
    orderby: {
      type: String,
      default: ''
    },
    where: {
      type: [String, Object],
      default: ''
    },
    pageData: {
      type: String,
      default: 'add'
    },
    pageCurrent: {
      type: Number,
      default: 1
    },
    pageSize: {
      type: Number,
      default: 20
    },
    getcount: {
      type: [Boolean, String],
      default: false
    },
    getone: {
      type: [Boolean, String],
      default: false
    },
    gettree: {
      type: [Boolean, String],
      default: false
    },
    manual: {
      type: Boolean,
      default: false
    },
    value: {
      type: [Array, String, Number],
      default () {
        return []
      }
    },
    modelValue: {
      type: [Array, String, Number],
      default () {
        return []
      }
    },
    preload: {
      type: Boolean,
      default: false
    },
    stepSearh: {
      type: Boolean,
      default: true
    },
    selfField: {
      type: String,
      default: ''
    },
    parentField: {
      type: String,
      default: ''
    },
    multiple: {
      type: Boolean,
      default: false
    },
    map: {
      type: Object,
      default() {
        return {
          text: "text",
          value: "value"
        }
      }
    }
  },
  data() {
    return {
      loading: false,
      errorMessage: '',
      loadMore: {
        contentdown: '',
        contentrefresh: '',
        contentnomore: ''
      },
      dataList: [],
      selected: [],
      selectedIndex: 0,
      page: {
        current: this.pageCurrent,
        size: this.pageSize,
        count: 0
      }
    }
  },
  computed: {
    isLocaldata() {
      return !this.collection.length
    },
    postField() {
      let fields = [this.field];
      if (this.parentField) {
        fields.push(`${this.parentField} as parent_value`);
      }
      return fields.join(',');
    },
    dataValue() {
      let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || this.modelValue !== undefined)
      return isModelValue ? this.modelValue : this.value
    },
    hasValue() {
      if (typeof this.dataValue === 'number') {
        return true
      }
      return (this.dataValue != null) && (this.dataValue.length > 0)
    }
  },
  created() {
    this.$watch(() => {
      var al = [];
      ['pageCurrent',
        'pageSize',
        'spaceInfo',
        'value',
        'modelValue',
        'localdata',
        'collection',
        'action',
        'field',
        'orderby',
        'where',
        'getont',
        'getcount',
        'gettree'
      ].forEach(key => {
        al.push(this[key])
      });
      return al
    }, (newValue, oldValue) => {
      let needReset = false
      for (let i = 2; i < newValue.length; i++) {
        if (newValue[i] != oldValue[i]) {
          needReset = true
          break
        }
      }
      if (newValue[0] != oldValue[0]) {
        this.page.current = this.pageCurrent
      }
      this.page.size = this.pageSize

      this.onPropsChange()
    })
    this._treeData = []
  },
  methods: {
    onPropsChange() {
      this._treeData = []
    },
    getCommand(options = {}) {
      /* eslint-disable no-undef */
      let db = uniCloud.database(this.spaceInfo)

      const action = options.action || this.action
      if (action) {
        db = db.action(action)
      }

      const collection = options.collection || this.collection
      db = db.collection(collection)

      const where = options.where || this.where
      if (!(!where || !Object.keys(where).length)) {
        db = db.where(where)
      }

      const field = options.field || this.field
      if (field) {
        db = db.field(field)
      }

      const orderby = options.orderby || this.orderby
      if (orderby) {
        db = db.orderBy(orderby)
      }

      const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
      const size = options.pageSize !== undefined ? options.pageSize : this.page.size
      const getCount = options.getcount !== undefined ? options.getcount : this.getcount
      const getTree = options.gettree !== undefined ? options.gettree : this.gettree

      const getOptions = {
        getCount,
        getTree
      }
      if (options.getTreePath) {
        getOptions.getTreePath = options.getTreePath
      }

      db = db.skip(size * (current - 1)).limit(size).get(getOptions)

      return db
    },
		getNodeData(callback) {
		  if (this.loading) {
		    return
		  }
		  this.loading = true
		  this.getCommand({
		    field: this.postField,
				where: this._pathWhere()
		  }).then((res) => {
		    this.loading = false
		    this.selected = res.result.data
		    callback && callback()
		  }).catch((err) => {
		    this.loading = false
		    this.errorMessage = err
		  })
		},
    getTreePath(callback) {
      if (this.loading) {
        return
      }
      this.loading = true

      this.getCommand({
        field: this.postField,
        getTreePath: {
          startWith: `${this.selfField}=='${this.dataValue}'`
        }
      }).then((res) => {
        this.loading = false
        let treePath = []
        this._extractTreePath(res.result.data, treePath)
        this.selected = treePath
        callback && callback()
      }).catch((err) => {
        this.loading = false
        this.errorMessage = err
      })
    },
    loadData() {
      if (this.isLocaldata) {
        this._processLocalData()
        return
      }

      if (this.dataValue != null) {
        this._loadNodeData((data) => {
          this._treeData = data
          this._updateBindData()
          this._updateSelected()
        })
        return
      }

      if (this.stepSearh) {
        this._loadNodeData((data) => {
          this._treeData = data
          this._updateBindData()
        })
      } else {
        this._loadAllData((data) => {
          this._treeData = []
          this._extractTree(data, this._treeData, null)
          this._updateBindData()
        })
      }
    },
    _loadAllData(callback) {
      if (this.loading) {
        return
      }
      this.loading = true

      this.getCommand({
        field: this.postField,
        gettree: true,
        startwith: `${this.selfField}=='${this.dataValue}'`
      }).then((res) => {
        this.loading = false
        callback(res.result.data)
        this.onDataChange()
      }).catch((err) => {
        this.loading = false
        this.errorMessage = err
      })
    },
    _loadNodeData(callback, pw) {
      if (this.loading) {
        return
      }
      this.loading = true

      this.getCommand({
        field: this.postField,
        where: pw || this._postWhere(),
        pageSize: 500
      }).then((res) => {
        this.loading = false
        callback(res.result.data)
        this.onDataChange()
      }).catch((err) => {
        this.loading = false
        this.errorMessage = err
      })
    },
    _pathWhere() {
      let result = []
      let where_field = this._getParentNameByField();
      if (where_field) {
        result.push(`${where_field} == '${this.dataValue}'`)
      }

      if (this.where) {
        return `(${this.where}) && (${result.join(' || ')})`
      }

      return result.join(' || ')
    },
    _postWhere() {
      let result = []
      let selected = this.selected
      let parentField = this.parentField
      if (parentField) {
        result.push(`${parentField} == null || ${parentField} == ""`)
      }
      if (selected.length) {
        for (var i = 0; i < selected.length - 1; i++) {
          result.push(`${parentField} == '${selected[i].value}'`)
        }
      }

      let where = []
      if (this.where) {
        where.push(`(${this.where})`)
      }
      if (result.length) {
        where.push(`(${result.join(' || ')})`)
      }

      return where.join(' && ')
    },
    _nodeWhere() {
      let result = []
      let selected = this.selected
      if (selected.length) {
        result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
      }

      if (this.where) {
        return `(${this.where}) && (${result.join(' || ')})`
      }

      return result.join(' || ')
    },
    _getParentNameByField() {
      const fields = this.field.split(',');
      let where_field = null;
      for (let i = 0; i < fields.length; i++) {
        const items = fields[i].split('as');
        if (items.length < 2) {
          continue;
        }
        if (items[1].trim() === 'value') {
          where_field = items[0].trim();
          break;
        }
      }
      return where_field
    },
    _isTreeView() {
      return (this.parentField && this.selfField)
    },
    _updateSelected() {
      var dl = this.dataList
      var sl = this.selected
      let textField = this.map.text
      let valueField = this.map.value
      for (var i = 0; i < sl.length; i++) {
        var value = sl[i].value
        var dl2 = dl[i]
        for (var j = 0; j < dl2.length; j++) {
          var item2 = dl2[j]
          if (item2[valueField] === value) {
            sl[i].text = item2[textField]
            break
          }
        }
      }
    },
    _updateBindData(node) {
      const {
        dataList,
        hasNodes
      } = this._filterData(this._treeData, this.selected)

      let isleaf = this._stepSearh === false && !hasNodes

      if (node) {
        node.isleaf = isleaf
      }

      this.dataList = dataList
      this.selectedIndex = dataList.length - 1

      if (!isleaf && this.selected.length < dataList.length) {
        this.selected.push({
          value: null,
          text: "请选择"
        })
      }

      return {
        isleaf,
        hasNodes
      }
    },
    _filterData(data, paths) {
      let dataList = []
      let hasNodes = true

      dataList.push(data.filter((item) => {
        return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
      }))
      for (let i = 0; i < paths.length; i++) {
        var value = paths[i].value
        var nodes = data.filter((item) => {
          return item.parent_value === value
        })

        if (nodes.length) {
          dataList.push(nodes)
        } else {
          hasNodes = false
        }
      }

      return {
        dataList,
        hasNodes
      }
    },
    _extractTree(nodes, result, parent_value) {
      let list = result || []
      let valueField = this.map.value
      for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i]

        let child = {}
        for (let key in node) {
          if (key !== 'children') {
            child[key] = node[key]
          }
        }
        if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
          child.parent_value = parent_value
        }
        result.push(child)

        let children = node.children
        if (children) {
          this._extractTree(children, result, node[valueField])
        }
      }
    },
    _extractTreePath(nodes, result) {
      let list = result || []
      for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i]

        let child = {}
        for (let key in node) {
          if (key !== 'children') {
            child[key] = node[key]
          }
        }
        result.push(child)

        let children = node.children
        if (children) {
          this._extractTreePath(children, result)
        }
      }
    },
    _findNodePath(key, nodes, path = []) {
      let textField = this.map.text
      let valueField = this.map.value
      for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i]
        let children = node.children
        let text = node[textField]
        let value = node[valueField]

        path.push({
          value,
          text
        })

        if (value === key) {
          return path
        }

        if (children) {
          const p = this._findNodePath(key, children, path)
          if (p.length) {
            return p
          }
        }

        path.pop()
      }
      return []
    },
    _processLocalData() {
      this._treeData = []
      this._extractTree(this.localdata, this._treeData)

      var inputValue = this.dataValue
      if (inputValue === undefined) {
        return
      }

      if (Array.isArray(inputValue)) {
        inputValue = inputValue[inputValue.length - 1]
        if (typeof inputValue === 'object' && inputValue[this.map.value]) {
          inputValue = inputValue[this.map.value]
        }
      }

      this.selected = this._findNodePath(inputValue, this.localdata)
    }
  }
}

调用页面

showSearch 是否显示搜索框和多选操作 =》 true/false

<template>
	<view>
			<view>{{treePickTitle}}</view>
			<uni-section :title="treePickTitle" type="line" padding >
				<uni-data-picker  :showSearch= "showSearch" :popup-title="treePickTitleSelect" :localdata="dataTree" v-model="activeDataTree"
					@change="onchange" @nodeclick="onnodeclick">
				</uni-data-picker>
			</uni-section>
	</view>
</template>

dataTree 格式例子

activeDataTree:"1-1",
  dataTree: [{
          text: "一年级",
          value: "1-0",
          children: [
            {
              text: "1.1班",
              value: "1-1"
            },
            {
              text: "1.2班",
              value: "1-2"
            }
          ]
        },
        {
          text: "二年级",
          value: "2-0"
        },
        {
          text: "三年级",
          value: "3-0"
        }]

bw:不想用text 和value 属性也可以修改


2022/08/19修改
由于很多小伙伴私信我说遇到问题,所以了解到大家可能需要的是不仅仅是搜索和多选的功能,也需要对应下面的标签新增,和删减功能,是我这边上传的视频造成误解啦,今天花费空余时间给大家写了个例子。复制粘贴可用:
步骤:

1,找到项目下的这些文件 把内容替换成我文章中的内容

在这里插入图片描述

2,调用页面全部代码

<template>
	<view class="container">
		<uni-card is-full :is-shadow="false">
			<text class="uni-h6">标签组件多用于商品分类、重点内容显示等场景。</text>
		</uni-card>
		<uni-section title="本地数据" type="line" padding style="height: calc(100vh - 100px);">
			<uni-data-picker :showSearch= "showSearch"  placeholder="请选择班级" popup-title="请选择所在地区" :localdata="dataTree" v-model="classes"
				@change="onchange" @nodeclick="onnodeclick" @popupopened="onpopupopened" @popupclosed="onpopupclosed">
			</uni-data-picker>
			<view class="userSelectBox example-body">
				   <view class="tag-view" v-for="(value,key) in userListSelect"  :key="key" @click="clearTagAdmin(value)">
						<uni-tag :text="userList[value]" type="primary" ></uni-tag>
						<uni-icons class="clearBtn"  type="clear" size="15" color="#ccc" />
					</view>
			</view>
		</uni-section>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				showSearch:true,
				userListSelect:[1,3],
				userList:['1.1班','1.2班','2.1班','2.2班'],
				classes: '1-2',
				dataTree: [{
						text: "一年级",
						value: "1-0",
						children: [{
								text: "1.1班",
								value: "1-1",
								parent_value:"1",
								id:0,
							},
							{
								text: "1.2班",
								value: "1-2",
								parent_value:"1",
								id:1,
							}
						]
					},
					{
						text: "二年级",
						value: "2-0",
						children: [{
								text: "2.1班",
								value: "2-1",
								parent_value:"2",
								id:2,
								
							},
							{
								text: "2.2班",
								value: "2-2",
								parent_value:"2",
								id:3
							}
						]
					},
					
				]
			}
		},
		methods: {
			clearTagAdmin(value){
				this.userListSelect = this.userListSelect.filter(item => item != value); 
			},
			onnodeclick(data) {
				console.log(data);
				if(data.parent_value){//存在父级
				  let dataId = data.id;
				  this.userListSelect.push(dataId);
				  this.userListSelect = [...new Set(this.userListSelect)]//es6数组去重
				}
			},
			onpopupopened(e) {
				console.log('popupopened');
			},
			onpopupclosed(e) {
				console.log('popupclosed');
			},
			onchange(e) {
				console.log('---------onchange:', e);
			}
		}
	}
</script>

<style>
	.title {
		font-size: 14px;
		font-weight: bold;
		margin: 20px 0 5px 0;
	}

	.data-pickerview {
		height: 400px;
		border: 1px #e5e5e5 solid;
	}

	.popper__arrow {
		top: -6px;
		left: 50%;
		margin-right: 3px;
		border-top-width: 0;
		border-bottom-color: #EBEEF5;
	}

	.popper__arrow {
		top: -6px;
		left: 50%;
		margin-right: 3px;
		border-top-width: 0;
		border-bottom-color: #EBEEF5;
	}
	.userSelectBox{
		border: 1px solid #ddd;
		width: 98%;
		min-height: 60px;
		padding: 5px 1%;
		border-radius:5px;
		overflow: hidden;
	}
	
	.tag-view{
		display: inline-block;
		float: left;
		margin-right:10px;
		margin-bottom:10px;
		height:24px;
		min-width:24px;
		max-width:50px;
		position:relative;
	}
	.clearBtn{
		position:absolute;
		right:-3px;
		top:-8px;
	}
	.example {
		padding: 15px;
		background-color: #fff;
	}
	
	.segmented-control {
		margin-bottom: 15px;
	}
	
	.button-group {
		margin-top: 15px;
		display: flex;
		justify-content: space-around;
	}
	
	.form-item {
		display: flex;
		align-items: center;
	}
	
	.button {
		display: flex;
		align-items: center;
		height: 35px;
		margin-left: 10px;
	}
	.selectBox {
		border: 1px solid #ddd;
		padding: 10px 1%;
		height: 14px;
		width: 98%;
		border-radius: 5px;
	}
</style>

3,可根据自己需求进行更改:呈现效果如下:
在这里插入图片描述

在这里插入图片描述
希望多多支持哈,点个赞再走 嘻嘻~码字不易。谢谢大家,有问题可私信

  • 37
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值