512 lines
14 KiB
Vue
512 lines
14 KiB
Vue
<template>
|
|
<u-popup mode="bottom" :show="show" @close="close">
|
|
<view class="da-tree">
|
|
<scroll-view scroll-y="true" style="height: 90vh;padding-left: 24rpx;">
|
|
<view class="da-tree-item" :class="{'is-show': datamap[item.key].show}"
|
|
:style="{paddingLeft: (item.level - 1) * indent + 'rpx'}" v-for="item in datalist">
|
|
<view v-if="item.children" class="da-tree-item__icon" @click="handleExpandedChange(item)">
|
|
<view v-if="datamap[item.key].loading" :class="['da-tree-item__icon--arr','is-loading']"></view>
|
|
<view v-else
|
|
:class="['da-tree-item__icon--arr','is-expand', {'is-right':!datamap[item.key].expand}]">
|
|
</view>
|
|
</view>
|
|
<view v-else class="da-tree-item__icon"></view>
|
|
<view class="iconfont icon-qiye" v-if="item.node?.groupStatus == 1" />
|
|
<view class="iconfont icon-gongsi" v-else-if="item.node?.groupStatus == 0" />
|
|
<view class="iconfont icon-nongtian" v-else />
|
|
<view class="da-tree-item__label" :class="'da-tree-item__label--'+item.checkedStatus"
|
|
@click="handleExpandedChange(item)">
|
|
<text :class="{'land-active':selectData?.id == item.id}">{{ item.label }}</text>
|
|
</view>
|
|
<uni-badge type="success" :text="item.node?.deviceCount" v-if="datamap[item.key].isLand" />
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</u-popup>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
unCheckedStatus,
|
|
halfCheckedStatus,
|
|
isCheckedStatus,
|
|
deepClone,
|
|
getAllNodeKeys,
|
|
getAllNodes,
|
|
logError,
|
|
isArray,
|
|
isString,
|
|
isNumber,
|
|
isFunction,
|
|
} from './utils'
|
|
export default {
|
|
name: 'custom-select-land',
|
|
emits: ["getLand", "select"],
|
|
props: {
|
|
/**
|
|
* 树的数据
|
|
*/
|
|
data: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
/**
|
|
* 默认选择中的id
|
|
*/
|
|
defaultSelect: {
|
|
type: Object,
|
|
default: {},
|
|
},
|
|
/**
|
|
* 默认展开第一级
|
|
*/
|
|
expandFirst: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
show: false,
|
|
indent: 30,
|
|
dataRef: [],
|
|
datalist: [],
|
|
datamap: {},
|
|
landmap: {},
|
|
selectData: {}
|
|
}
|
|
},
|
|
watch: {
|
|
data: {
|
|
deep: true,
|
|
immediate: true,
|
|
handler: function(v) {
|
|
this.dataRef = deepClone(v)
|
|
setTimeout(() => {
|
|
this.initData()
|
|
}, 36)
|
|
},
|
|
},
|
|
},
|
|
created() {},
|
|
methods: {
|
|
//打开弹窗
|
|
open() {
|
|
this.show = true;
|
|
if (!this.selectData?.id) {
|
|
this.selectData = this.defaultSelect;
|
|
}
|
|
},
|
|
//关闭弹窗
|
|
close() {
|
|
this.show = false;
|
|
},
|
|
/**
|
|
* 初始化数据结构
|
|
*/
|
|
initData() {
|
|
const data = deepClone(this.dataRef);
|
|
this.datalist = [];
|
|
this.handleTreeData(data);
|
|
// 展开第一个
|
|
if (this.expandFirst && this.datalist.length) {
|
|
this.handleExpandedChange(this.datalist[0]);
|
|
}
|
|
// console.log('init datalist', this.datalist)
|
|
// console.log('init datamap', this.datamap)
|
|
},
|
|
/**
|
|
* 转换为节点数据
|
|
* @param data
|
|
* @param parent
|
|
* @param level
|
|
*/
|
|
handleTreeData(data = []) {
|
|
const that = this;
|
|
|
|
function traverse(node, parent = null) {
|
|
const children = Array.isArray(node.children) ? node.children : [];
|
|
const childrenIds = children.map(child => `c${child.id}`);
|
|
|
|
node = {
|
|
...node,
|
|
key: `c${node.id}`,
|
|
parent: parent,
|
|
level: node.node.level,
|
|
keys: childrenIds,
|
|
};
|
|
that.datalist.push(node);
|
|
that.datamap[node.key] = that.createMapItem(node);
|
|
if (!parent) {
|
|
that.datamap[node.key].show = true;
|
|
}
|
|
|
|
children.forEach(child => {
|
|
traverse(child, node.key);
|
|
});
|
|
}
|
|
|
|
data.forEach(item => {
|
|
if (item.node.parentId > 0) {
|
|
traverse(item, `c${item.node.parentId}`)
|
|
} else {
|
|
traverse(item)
|
|
}
|
|
|
|
});
|
|
},
|
|
/**
|
|
* 创建节点
|
|
* @param item
|
|
* @param isLand
|
|
*/
|
|
createMapItem(item, isLand = false) {
|
|
return Object.assign({}, item, {
|
|
show: false, //显示
|
|
expand: false, //打开
|
|
isLand: isLand, //是地块
|
|
loading: false, //加载
|
|
isloaded: false, //上是否加载过数据了(调用过数据接口才算)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 点击展开收起
|
|
* @param item
|
|
*/
|
|
async handleExpandedChange(item) {
|
|
const {
|
|
expand,
|
|
loading,
|
|
isLand
|
|
} = this.datamap[item.key];
|
|
if (loading) return;
|
|
// 如果是地块,则直接跳出
|
|
if (isLand) {
|
|
this.selectLand(item)
|
|
return;
|
|
}
|
|
|
|
// 不是地块加载地块数据
|
|
if (item.parent && !expand) {
|
|
await this.loadExpandNode(item)
|
|
}
|
|
this.checkExpandedChange(this.datamap[item.key], !expand)
|
|
this.datamap[item.key].expand = !expand;
|
|
},
|
|
|
|
/**
|
|
* 加载异步地块数据
|
|
* @param item
|
|
*/
|
|
async loadExpandNode(item) {
|
|
// 如果已经加载过数据,则跳出
|
|
if (this.datamap[item.key].isloaded) {
|
|
return;
|
|
}
|
|
|
|
this.datamap[item.key].loading = true;
|
|
// if (item.lands) return;
|
|
// 如果已经有地块数据,则先使用此数据
|
|
if (item.lands) {
|
|
this.addLand(item, item.lands, false);
|
|
}
|
|
// 如果没更新过数据,则异步的去更新数据
|
|
this.$emit('getLand', item);
|
|
},
|
|
/**
|
|
* 添加地块
|
|
* @param item
|
|
* @param lands
|
|
* @param isloaded 是否接口加载的数据
|
|
*/
|
|
addLand(item, lands, isloaded = true) {
|
|
const that = this;
|
|
that.datamap[item.key].loading = false;
|
|
if (!lands || !lands.length) {
|
|
return;
|
|
}
|
|
|
|
that.datamap[item.key].isloaded = isloaded;
|
|
const oldLand = item?.lands || null;
|
|
item.lands = lands;
|
|
|
|
const index = that.datalist.findIndex(it => it.id === item.id);
|
|
// 如果存在地块数据,则删除已经存在的
|
|
if (oldLand && oldLand.length) {
|
|
oldLand.forEach(land => {
|
|
if (that.datamap[`l${land.id}`]) {
|
|
that.datalist.splice(index + 1, 1);
|
|
}
|
|
});
|
|
}
|
|
if (index !== -1) {
|
|
let addLand = [];
|
|
lands.forEach(land => {
|
|
land = {
|
|
id: land.id,
|
|
label: land.landName,
|
|
node: land,
|
|
key: `l${land.id}`,
|
|
parent: item.key,
|
|
level: item.node.level + 1,
|
|
keys: [],
|
|
};
|
|
addLand.push(land);
|
|
|
|
that.datamap[item.key].keys.push(`l${land.id}`);
|
|
|
|
let newItem = that.createMapItem(land, true);
|
|
newItem.show = that.datamap[item.key].show;
|
|
that.datamap[land.key] = newItem;
|
|
});
|
|
that.datalist.splice(index + 1, 0, ...addLand);
|
|
item.keys = that.datamap[item.key].keys;
|
|
}
|
|
},
|
|
// 选中地块数据
|
|
selectLand(item) {
|
|
this.close();
|
|
if (item.id == this.selectData?.id) {
|
|
return;
|
|
}
|
|
this.selectData = item.node;
|
|
this.$emit('select', item.node);
|
|
uni.$emit('notify-update-land', item.node);
|
|
},
|
|
|
|
/**
|
|
* 检查展开状态
|
|
* @param item
|
|
*/
|
|
checkExpandedChange(datamap, isShow) {
|
|
const {
|
|
keys,
|
|
} = datamap;
|
|
|
|
if (keys?.length) {
|
|
keys.forEach(k => {
|
|
if (this.datamap[k]) {
|
|
this.datamap[k].show = isShow;
|
|
if (this.datamap[k].expand) {
|
|
this.checkExpandedChange(this.datamap[k], isShow);
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@font-face {
|
|
font-family: 'da-tree-iconfont';
|
|
/* Project id */
|
|
src: url('data:application/octet-stream;base64,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') format('truetype');
|
|
}
|
|
|
|
.da-tree {
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
&-scroll {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
&-item {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
font-size: 28rpx;
|
|
line-height: 1;
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transition: opacity 0.2s linear;
|
|
|
|
&.is-show {
|
|
height: auto;
|
|
padding: 12rpx 24rpx;
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
&__icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
overflow: hidden;
|
|
|
|
&--arr {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
|
|
&::after {
|
|
position: relative;
|
|
z-index: 1;
|
|
overflow: hidden;
|
|
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
|
font-family: 'da-tree-iconfont' !important;
|
|
font-size: 32rpx;
|
|
font-style: normal;
|
|
color: #999;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
&.is-expand {
|
|
&::after {
|
|
content: '\e604';
|
|
}
|
|
}
|
|
|
|
&.is-right {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
&.is-loading {
|
|
animation: IconLoading 1s linear 0s infinite;
|
|
|
|
&::after {
|
|
content: '\e7f1';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
&__checkbox {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
overflow: hidden;
|
|
|
|
&--left {
|
|
order: 0;
|
|
}
|
|
|
|
&--right {
|
|
order: 1;
|
|
}
|
|
|
|
&--icon {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
|
|
&::after {
|
|
position: relative;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 1;
|
|
overflow: hidden;
|
|
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
|
font-family: 'da-tree-iconfont' !important;
|
|
font-size: 32rpx;
|
|
font-style: normal;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
&.da-tree-checkbox-outline::after {
|
|
color: #bbb;
|
|
content: '\ead5';
|
|
}
|
|
|
|
&.da-tree-checkbox-checked::after {
|
|
color: var(--theme-color, #007aff);
|
|
content: '\ead4';
|
|
}
|
|
|
|
&.da-tree-checkbox-indeterminate::after {
|
|
color: var(--theme-color, #007aff);
|
|
content: '\ebce';
|
|
}
|
|
|
|
&.da-tree-radio-outline::after {
|
|
color: #bbb;
|
|
content: '\ecc5';
|
|
}
|
|
|
|
&.da-tree-radio-checked::after {
|
|
color: var(--theme-color, #007aff);
|
|
content: '\ecc4';
|
|
}
|
|
|
|
&.da-tree-radio-indeterminate::after {
|
|
color: var(--theme-color, #007aff);
|
|
content: '\ea4f';
|
|
}
|
|
}
|
|
|
|
&.is--disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.35;
|
|
}
|
|
}
|
|
|
|
&__label {
|
|
flex: 1;
|
|
margin-left: 4rpx;
|
|
color: #555;
|
|
|
|
&--2 {
|
|
color: var(--theme-color, #007aff);
|
|
}
|
|
|
|
&--append {
|
|
font-size: 60%;
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes IconLoading {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
// 三角箭头
|
|
// .da-tree-item__icon {
|
|
// display: none;
|
|
// }
|
|
|
|
// icon
|
|
.iconfont {
|
|
padding-right: 12rpx;
|
|
}
|
|
|
|
.icon-qiye,
|
|
.icon-gongsi {
|
|
color: #399bfe;
|
|
}
|
|
|
|
.icon-nongtian {
|
|
color: #39ac4f;
|
|
}
|
|
|
|
.land-active {
|
|
color: #39ac4f;
|
|
}
|
|
|
|
// 数字角标
|
|
::v-deep.uni-badge--success {
|
|
background-color: #39ac4f !important;
|
|
}
|
|
</style> |