SmartStorage/uni_modules/custom-tree-select/components/custom-tree-select/data-select-item.vue

364 lines
7.8 KiB
Vue

<template>
<view
class="custom-tree-select-content"
:class="{
border:
border &&
node[dataChildren] &&
node[dataChildren].length &&
node.showChildren
}"
:style="{ marginLeft: `${level ? 14 : 0}px` }"
>
<view v-if="node.visible" class="custom-tree-select-item">
<view class="item-content">
<view class="left">
<view
v-if="node[dataChildren] && node[dataChildren].length"
:class="['right-icon', { active: node.showChildren }]"
@click.stop="nameClick(node)"
>
<uni-icons type="forward" size="14" color="#333"></uni-icons>
</view>
<view v-else class="smallcircle-filled">
<uni-icons
class="smallcircle-filled-icon"
type="smallcircle-filled"
size="10"
color="#333"
></uni-icons>
</view>
<view
v-if="loadingArr.includes(node[dataValue])"
class="loading-icon-box"
>
<uni-icons
class="loading-icon"
type="spinner-cycle"
size="14"
color="#333"
></uni-icons>
</view>
<view
class="name"
:style="node.disabled ? 'color: #999' : ''"
@click.stop="nameClick(node)"
>
<text>{{ node[dataLabel] }}</text>
</view>
</view>
<view
v-if="
choseParent ||
(!choseParent && !node[dataChildren]) ||
(!choseParent && node[dataChildren] && !node[dataChildren].length)
"
:class="['check-box', { disabled: node.disabled }]"
@click.stop="nodeClick(node)"
>
<view
v-if="!node.checked && node.partChecked && linkage"
class="part-checked"
></view>
<uni-icons
v-if="node.checked"
type="checkmarkempty"
size="18"
:color="node.disabled ? '#333' : '#007aff'"
></uni-icons>
</view>
</view>
</view>
<view
v-if="
node.showChildren && node[dataChildren] && node[dataChildren].length
"
>
<data-select-item
v-for="item in listData"
:key="item[dataValue]"
:node="item"
:dataLabel="dataLabel"
:dataValue="dataValue"
:dataChildren="dataChildren"
:choseParent="choseParent"
:border="border"
:linkage="linkage"
:level="level + 1"
:load="load"
:lazyLoadChildren="lazyLoadChildren"
></data-select-item>
</view>
</view>
</template>
<script>
import dataSelectItem from './data-select-item.vue'
import { paging } from './utils'
export default {
name: 'data-select-item',
components: {
'data-select-item': dataSelectItem
},
props: {
node: {
type: Object,
default: () => ({})
},
choseParent: {
type: Boolean,
default: true
},
dataLabel: {
type: String,
default: 'name'
},
dataValue: {
type: String,
default: 'value'
},
dataChildren: {
type: String,
default: 'children'
},
border: {
type: Boolean,
default: false
},
linkage: {
type: Boolean,
default: false
},
level: {
type: Number,
default: 0
},
load: {
type: Function,
default: function () {}
},
lazyLoadChildren: {
type: Boolean,
default: false
}
},
data() {
return {
listData: [],
clearTimerList: [],
loadingArr: []
}
},
computed: {
watchData() {
const { node, dataChildren } = this
return {
node,
dataChildren
}
}
},
watch: {
watchData: {
immediate: true,
handler(newVal) {
const { node, dataChildren } = newVal
if (
node.showChildren &&
node[dataChildren] &&
node[dataChildren].length
) {
this.resetClearTimerList()
this.renderTree(node[dataChildren])
}
}
}
},
methods: {
// 懒加载
renderTree(arr) {
const pagingArr = paging(arr)
this.listData.splice(0, this.listData.length, ...(pagingArr?.[0] || []))
this.lazyRenderList(pagingArr, 1)
},
// 懒加载具体逻辑
lazyRenderList(arr, startIndex) {
for (let i = startIndex; i < arr.length; i++) {
let timer = null
timer = setTimeout(() => {
this.listData.push(...arr[i])
}, i * 500)
this.clearTimerList.push(() => clearTimeout(timer))
}
},
// 中断懒加载
resetClearTimerList() {
const list = [...this.clearTimerList]
this.clearTimerList.splice(0, this.clearTimerList.length)
list.forEach((item) => item())
},
async nameClick(node) {
if (!node[this.dataChildren]?.length && this.lazyLoadChildren) {
this.loadingArr.push(node[this.dataValue])
try {
const res = await this.load(node)
if (Array.isArray(res)) {
uni.$emit('custom-tree-select-load', {
source: node,
target: res
})
}
} finally {
this.loadingArr = []
}
} else {
if (
!node.showChildren &&
node[this.dataChildren] &&
node[this.dataChildren].length
) {
// 打开
this.renderTree(node[this.dataChildren])
} else {
// 关闭
this.resetClearTimerList()
this.listData.splice(0, this.listData.length)
}
uni.$emit('custom-tree-select-name-click', node)
}
},
nodeClick(node) {
if (!node.disabled) {
uni.$emit('custom-tree-select-node-click', node)
}
}
},
options: {
styleIsolation: 'shared'
}
}
</script>
<style lang="scss" scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
$primary-color: #007aff;
$col-sm: 4px;
$col-base: 8px;
$col-lg: 12px;
$row-sm: 5px;
$row-base: 10px;
$row-lg: 15px;
$radius-sm: 3px;
$radius-base: 6px;
$border-color: #c8c7cc;
.custom-tree-select-content {
&.border {
border-left: 1px solid $border-color;
}
/deep/ .uni-checkbox-input {
margin: 0 !important;
}
.item-content {
margin: 0 0 $col-lg;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 3px;
background-color: #fff;
transform: translateX(-2px);
z-index: 1;
}
.left {
flex: 1;
display: flex;
align-items: center;
.right-icon {
transition: 0.15s ease;
&.active {
transform: rotate(90deg);
}
}
.smallcircle-filled {
width: 14px;
height: 13.6px;
display: flex;
align-items: center;
.smallcircle-filled-icon {
transform-origin: center;
transform: scale(0.55);
}
}
.loading-icon-box {
margin-right: $row-sm;
width: 14px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.loading-icon {
transform-origin: center;
animation: rotating infinite 0.2s ease;
}
}
.name {
flex: 1;
}
}
}
}
.check-box {
width: 23.6px;
height: 23.6px;
border: 1px solid $border-color;
border-radius: $radius-sm;
display: flex;
justify-content: center;
align-items: center;
&.disabled {
background-color: rgb(225, 225, 225);
}
.part-checked {
width: 60%;
height: 2px;
background-color: $primary-color;
}
}
@keyframes rotating {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
</style>