Files
2026-09-24 16:25:22 +08:00

346 lines
9.2 KiB
Plaintext

<script lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue"
import { getUid, rpx2px, checkIsCssUnit, getUnit } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { SLIDER_TREE_ITEM_INFO } from "../../interface.uts"
import { SLIDER_TREE_ITEM } from "../x-slider-tree/interface.uts"
import { getTreeSelectedNum } from "../x-slider-tree/util.uts"
/**
* @name 侧边分类子节点 xSliderTreeChildren
* @description xSliderTree内部子组件,不可引用
* @page /pages/index/slider-tree
* @category 导航组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xSliderTreeChildren"})
defineSlots<{
default(): any
}>()
const emits = defineEmits([
/**
* 返回上级
*/
"back",
/**
* 项目点击事件
* @param {string[]} ids - 选中的id数组
* @param {string} type - 事件类型
*/
"itemClick"
])
type xSliderChildrenPropsType = {
/**
* 选项项目未选中的文字颜色
*/
itemTextColor: string,
/**
* 选项项目选中的文字颜色,空值取全局主题
*/
itemActiveColor: string,
/**
* 右内容区域背景颜色
*/
sliderContentBgColor: string,
/**
* 数据列表
*/
list: SLIDER_TREE_ITEM[],
/**
* 当前选中项的id数组
*/
modelValue: string[],
/**
* 是否允许多选
*/
multiple: boolean,
/**
* 当前的索引
*/
index: number,
/**
* 父级选中的项。
*/
nowSelecteds: string[],
/**
* 高度
*/
height: string,
/**
* 容器高度
*/
boxHeight: number,
/**
* 文字大小
*/
fontSize: string
}
const props = withDefaults(defineProps<xSliderChildrenPropsType>(), {
itemTextColor: "#333333",
itemActiveColor: "",
sliderContentBgColor: "white",
list: [] as SLIDER_TREE_ITEM[],
modelValue: [] as string[],
multiple: false,
index: 0,
nowSelecteds: [] as string[],
height: '100%',
boxHeight: 0,
fontSize: "16"
})
// 响应式数据
const activeIndex = ref<number>(-1)
const selectedsIds = ref<string[]>([])
const headHeight = ref<number>(rpx2px(76))
const show = ref<boolean>(false)
const tid = ref<number>(12)
// 计算属性
const _height = computed((): string => {
return props.height
})
const _boxHeight = computed((): number => {
return props.boxHeight
})
const _boxCalcHeight = computed((): string => {
return props.index > 0 ? ((_boxHeight.value - headHeight.value).toString() + 'px') : _height.value
})
const _parentIndex = computed((): number => {
return props.index
})
const _itemTextColor = computed((): string => {
if (xConfig.dark == 'dark') return '#fff'
return getDefaultColor(props.itemTextColor);
})
const _itemActiveColor = computed((): string => {
return props.itemActiveColor != "" ? getDefaultColor(props.itemActiveColor) : getDefaultColor(xConfig.color);
})
const _sliderContentBgColor = computed((): string => {
return getDefaultColor(props.sliderContentBgColor);
})
const _multiple = computed((): boolean => {
return props.multiple
})
const _list = computed((): SLIDER_TREE_ITEM[] => {
return props.list
})
/**
* 所有选中的id。
*/
const _nowSelecteds = computed((): string[] => {
return props.nowSelecteds.slice(0)
})
const _isNodata = computed((): boolean => {
if (activeIndex.value == -1 || _list.value.length == 0 || activeIndex.value > _list.value.length - 1) return false;
if (_list.value[activeIndex.value].children.length == 0) return false;
return true;
})
const _fontSize = computed((): string => {
return props.fontSize
})
const _yixuanFontsize = computed((): string => {
return (14 * xConfig.fontScale).toString() + 'px'
})
// 方法
/**
* 当前是否选中
*/
const isSelected = (item: SLIDER_TREE_ITEM): boolean => {
return (_nowSelecteds.value.includes(item.id) || selectedsIds.value.includes(item.id)) && item.children.length == 0
}
/**
* 本下级选了几个
*/
const isSelectedNum = (item: SLIDER_TREE_ITEM): number => {
let ps = new Set(_nowSelecteds.value.concat(selectedsIds.value))
return getTreeSelectedNum(item.children, ps);
}
const onClick = (item: SLIDER_TREE_ITEM, index: number): void => {
if (item.disabled) return;
activeIndex.value = index;
// 如果还有下一级,就不要向上汇报数据,直到最后一级。
if (item.children.length == 0) {
if (_multiple.value) {
if (selectedsIds.value.includes(item.id)) {
let index = selectedsIds.value.findIndex((el: string): boolean => el == item.id);
if (index > -1) {
selectedsIds.value.splice(index, 1)
}
} else {
selectedsIds.value.push(item.id)
}
} else {
selectedsIds.value = [item.id]
}
emits("itemClick", selectedsIds.value, 'click')
}
}
const backParent = (): void => {
emits("itemClick", selectedsIds.value, 'back')
activeIndex.value = -1;
}
const childrenClick = (id: string[], types?: string): void => {
let type = types == null ? 'click' : 'back';
// 4.02开始,不知道为什么递归的子组件,递归几次,会被触发几次。
if (_multiple.value) {
if (_list.value[activeIndex.value].children.length > 0) {
selectedsIds.value = id
} else {
// 给当前级加上选中的子组级id
let ps = new Set(id.concat(selectedsIds.value))
let oks = [] as string[]
ps.forEach((el: string) => {
oks.push(el)
})
selectedsIds.value = oks
}
emits("itemClick", selectedsIds.value, type)
} else {
selectedsIds.value = id;
emits("itemClick", selectedsIds.value, type)
}
}
// 监听器
watch((): string[] => props.nowSelecteds, () => {
selectedsIds.value = _nowSelecteds.value.slice(0);
})
// 生命周期
onMounted(() => {
selectedsIds.value = _nowSelecteds.value.slice(0);
clearTimeout(tid.value)
tid.value = setTimeout(() => {
show.value = true;
}, 200)
})
onBeforeUnmount(() => {
clearTimeout(tid.value)
})
defineExpose({
/** 获取当前选中的项 **/
getSelected: (): string[] => selectedsIds.value,
/** 设置选中的项 **/
setSelected: (ids: string[]): void => {
selectedsIds.value = ids;
}
})
</script>
<template>
<view class="xSliderTreeItem">
<!--
@slot 内部节点插槽,不对外
-->
<slot></slot>
<!-- &&show -->
<list-view class="xSliderTreeItem" :style="{height:_boxCalcHeight}">
<list-item :hover-start-time="10" :hover-stay-time="100"
:hover-class="item.disabled?'':'xSliderTreeItemHover'" @click="onClick(item,index)"
v-for="(item,index) in _list" :key="index" class="xSliderTreeItemRight"
:style="{backgroundColor:_sliderContentBgColor,opacity:item.disabled?'0.5':1}">
<text class="xSliderTreeItemRightText"
:style="{fontSize:_fontSize,color:isSelected(item)?_itemActiveColor : _itemTextColor}">{{item.title}}</text>
<view style="display: flex;flex-direction: row;justify-content: flex-end;align-items: center;">
<text :style="{fontSize:_yixuanFontsize,color:_itemActiveColor,marginRight:'6px'}"
v-if="item.children.length>0&&isSelectedNum(item)>0">已选({{isSelectedNum(item)}})</text>
<x-icon v-if="isSelected(item)" font-size="16" :color="_itemActiveColor" name="check-line"></x-icon>
<x-icon v-if="item.children.length>0" :color="_itemTextColor" name="arrow-right-s-line"></x-icon>
</view>
</list-item>
</list-view>
<view @click.stop="" class="xSliderTreeItemMore" v-if="_isNodata"
:style="{backgroundColor:_sliderContentBgColor}">
<view v-if="_list[activeIndex].children.length>0" style="flex:1">
<x-slider-children @back="backParent" :boxHeight="_boxHeight" :height="_height"
@itemClick="childrenClick" :nowSelecteds="_nowSelecteds" :multiple="_multiple"
:sliderContentBgColor="_sliderContentBgColor" :itemActiveColor="_itemActiveColor"
:itemTextColor="_itemTextColor" :index="_parentIndex+1" :list="_list[activeIndex].children">
<view @click="backParent" class="xSliderTreeItemMoreHeader">
<x-icon :color="_itemActiveColor" font-size="18" name="arrow-left-s-line"></x-icon>
<text
:style="{fontSize:_fontSize,color:_itemActiveColor,marginLeft:'5px',fontWeight:'bold'}">返回上级({{_list[activeIndex].title}})</text>
</view>
</x-slider-children>
</view>
</view>
</view>
</template>
<style scoped>
.xSliderTreeItemHover {
background-color: rgba(125, 125, 125, 0.1);
}
.xSliderTreeItemMoreHeader {
padding: 0 12px;
display: flex;
/* #ifndef APP */
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
}
.xSliderTreeItemMore {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
z-index: 10;
}
.xSliderTreeItem {
width: 100%;
height: 100%;
position: relative;
}
.xSliderTreeItemRight {
padding: 0 12px;
height: 50px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.xSliderTreeItemRightText {
text-align: left;
}
</style>