416 lines
12 KiB
Plaintext
416 lines
12 KiB
Plaintext
<script lang="ts" setup>
|
|
import { ref, computed, watch, onMounted, onBeforeUnmount, getCurrentInstance } from "vue"
|
|
import { getUid } from "../../core/util/xCoreUtil.uts"
|
|
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
|
|
import { checkIsCssUnit, getUnit } from "../../core/util/xCoreUtil.uts"
|
|
import { xConfig } from "../../config/xConfig.uts"
|
|
import { SLIDER_TREE_ITEM_INFO } from "../../interface.uts"
|
|
import { SLIDER_TREE_ITEM } from "./interface.uts"
|
|
import { getTreeSelectedNum, filterParentNode } from "./util.uts"
|
|
type addOptionalFieldsToTreeCall = (tree: SLIDER_TREE_ITEM_INFO[]) => void;
|
|
type addOptionalFieldsToTreeCloloneCall = (tree: SLIDER_TREE_ITEM_INFO[]) => SLIDER_TREE_ITEM[];
|
|
|
|
/**
|
|
* @name 侧边分类 xSliderTree
|
|
* @description 侧边分类选择,可多选,单选模式。
|
|
* @page /pages/index/slider-tree
|
|
* @category 导航组件
|
|
* @constant 平台兼容
|
|
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
|
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
|
|
*/
|
|
defineOptions({name:"xSliderTree"})
|
|
|
|
const proxy = getCurrentInstance()?.proxy ?? null;
|
|
|
|
defineSlots<{
|
|
default(): any
|
|
}>()
|
|
|
|
const emits = defineEmits([
|
|
/**
|
|
* 选中变换时触发
|
|
* @param {string[]} ids - 当前选中的项
|
|
*/
|
|
'change',
|
|
/**
|
|
* 等同v-model:modelValue
|
|
*/
|
|
'update:modelValue'
|
|
])
|
|
|
|
type xSliderTreePropsType = {
|
|
/**
|
|
* 宽
|
|
*/
|
|
width: string,
|
|
/**
|
|
* 高是必填,不可为auto。
|
|
*/
|
|
height: string,
|
|
/**
|
|
* 侧边选中的文字颜色,空值取全局主题
|
|
*/
|
|
activeTextColor: string,
|
|
/**
|
|
* 侧边未选中时的文字颜色
|
|
*/
|
|
textColor: string,
|
|
/**
|
|
* 选项项目未选中的文字颜色
|
|
*/
|
|
itemTextColor: string,
|
|
/**
|
|
* 选项项目选中的文字颜色,空值取全局主题
|
|
*/
|
|
itemActiveColor: string,
|
|
/**
|
|
* 左侧边栏背景颜色
|
|
*/
|
|
sliderBgColor: string,
|
|
/**
|
|
* 左侧边栏暗黑背景颜色
|
|
* 如果不提供,自动读取全局的backgroundColorContentDark背景色
|
|
*/
|
|
darkSliderBgColor: string,
|
|
/**
|
|
* 右内容区域背景颜色
|
|
*/
|
|
sliderContentBgColor: string,
|
|
/**
|
|
* 右内容区域暗黑背景颜色
|
|
* 如果不提供读取sheet窗口的暗黑背景
|
|
*/
|
|
darkSliderContentBgColor: string,
|
|
/**
|
|
* 侧边栏宽
|
|
*/
|
|
sliderWidth: string,
|
|
/**
|
|
* 数据列表
|
|
*/
|
|
list: SLIDER_TREE_ITEM_INFO[],
|
|
/**
|
|
* 当前选中项的id数组
|
|
*/
|
|
modelValue: string[],
|
|
/**
|
|
* 每级是否允许多选
|
|
*/
|
|
multiple: boolean,
|
|
/**
|
|
* 文字大小
|
|
*/
|
|
fontSize: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<xSliderTreePropsType>(), {
|
|
width: "auto",
|
|
height: "100%",
|
|
activeTextColor: "",
|
|
textColor: "#888888",
|
|
itemTextColor: "#333333",
|
|
itemActiveColor: "",
|
|
sliderBgColor: "#f5f5f5",
|
|
darkSliderBgColor: "",
|
|
sliderContentBgColor: "white",
|
|
darkSliderContentBgColor: "",
|
|
sliderWidth: "100",
|
|
list: [] as SLIDER_TREE_ITEM_INFO[],
|
|
modelValue: [] as string[],
|
|
multiple: false,
|
|
fontSize: "16"
|
|
})
|
|
|
|
// 响应式数据
|
|
const activeIndex = ref<number>(-1)
|
|
const selectedsIds = ref<string[]>([])
|
|
const nrHeight = ref<string>('100%')
|
|
const boxHeight = ref<number>(0)
|
|
const tid = ref<number>(0)
|
|
|
|
// 计算属性
|
|
const _fontSize = computed((): string => {
|
|
let fontSize = checkIsCssUnit(props.fontSize, xConfig.unit);
|
|
if (xConfig.fontScale == 1) return fontSize;
|
|
let sizeNumber = parseInt(fontSize)
|
|
if (isNaN(sizeNumber)) {
|
|
sizeNumber = 16
|
|
}
|
|
return (sizeNumber * xConfig.fontScale).toString() + getUnit(fontSize)
|
|
})
|
|
|
|
const _sliderWidth = computed((): string => {
|
|
return checkIsCssUnit(props.sliderWidth, xConfig.unit);
|
|
})
|
|
|
|
const _width = computed((): string => {
|
|
return checkIsCssUnit(props.width, xConfig.unit);
|
|
})
|
|
|
|
const _height = computed((): string => {
|
|
return checkIsCssUnit(props.height, xConfig.unit);
|
|
})
|
|
|
|
const _activeTextColor = computed((): string => {
|
|
return props.activeTextColor != "" ? getDefaultColor(props.activeTextColor) : getDefaultColor(xConfig.color);
|
|
})
|
|
|
|
const _textColor = computed((): string => {
|
|
return getDefaultColor(props.textColor);
|
|
})
|
|
|
|
const _itemTextColor = computed((): string => {
|
|
return getDefaultColor(props.itemTextColor);
|
|
})
|
|
|
|
const _itemActiveColor = computed((): string => {
|
|
return props.itemActiveColor != "" ? getDefaultColor(props.itemActiveColor) : getDefaultColor(xConfig.color);
|
|
})
|
|
|
|
const _sliderBgColor = computed((): string => {
|
|
if (xConfig.dark == 'dark') {
|
|
if (props.darkSliderBgColor != '') {
|
|
return getDefaultColor(props.darkSliderBgColor)
|
|
} else {
|
|
return getDefaultColor(xConfig.backgroundColorContentDark)
|
|
}
|
|
}
|
|
return getDefaultColor(props.sliderBgColor);
|
|
})
|
|
|
|
const _sliderContentBgColor = computed((): string => {
|
|
if (xConfig.dark == 'dark') {
|
|
if (props.darkSliderContentBgColor != '') {
|
|
return getDefaultColor(props.darkSliderContentBgColor)
|
|
} else {
|
|
return getDefaultColor(xConfig.sheetDarkColor)
|
|
}
|
|
}
|
|
return getDefaultColor(props.sliderContentBgColor);
|
|
})
|
|
|
|
const _multiple = computed((): boolean => {
|
|
return props.multiple
|
|
})
|
|
let addOptionalFieldsToTree:addOptionalFieldsToTreeCall|null = null;
|
|
let addOptionalFieldsToTreeClolone:addOptionalFieldsToTreeCloloneCall|null = null;
|
|
|
|
const _list = computed((): SLIDER_TREE_ITEM[] => {
|
|
let list = props.list as SLIDER_TREE_ITEM_INFO[];
|
|
|
|
addOptionalFieldsToTree = (tree: SLIDER_TREE_ITEM_INFO[]) =>{
|
|
for (let i = 0; i < tree.length; i++) {
|
|
const node = tree[i];
|
|
node.disabled = node.disabled == null ? false : node.disabled! as boolean;
|
|
node.selected = node.selected == null ? [] : node.selected! as string[];
|
|
node.children = node.children == null ? ([] as SLIDER_TREE_ITEM_INFO[]) : node.children! as SLIDER_TREE_ITEM_INFO[];
|
|
if ((node.children!).length > 0) {
|
|
addOptionalFieldsToTree!(node.children! as SLIDER_TREE_ITEM_INFO[]);
|
|
}
|
|
}
|
|
}
|
|
|
|
addOptionalFieldsToTreeClolone = (tree: SLIDER_TREE_ITEM_INFO[]): SLIDER_TREE_ITEM[] => {
|
|
let nowlist = [] as SLIDER_TREE_ITEM[]
|
|
for (let i = 0; i < tree.length; i++) {
|
|
const node = tree[i];
|
|
node.disabled = node.disabled == null ? false : node.disabled! as boolean;
|
|
node.selected = node.selected == null ? [] : node.selected! as string[];
|
|
node.children = node.children == null ? ([] as SLIDER_TREE_ITEM_INFO[]) : node.children! as SLIDER_TREE_ITEM_INFO[];
|
|
let item = {
|
|
id: node.id,
|
|
title: node.title,
|
|
disabled: node.disabled!,
|
|
selected: node.selected!,
|
|
children: [] as SLIDER_TREE_ITEM[],
|
|
icon: node?.icon ?? ""
|
|
} as SLIDER_TREE_ITEM
|
|
if ((node.children!).length > 0) {
|
|
item.children = addOptionalFieldsToTreeClolone!(node.children! as SLIDER_TREE_ITEM_INFO[]);
|
|
}
|
|
nowlist.push(item)
|
|
}
|
|
|
|
return nowlist
|
|
}
|
|
|
|
addOptionalFieldsToTree!(list as SLIDER_TREE_ITEM_INFO[]);
|
|
|
|
return addOptionalFieldsToTreeClolone!(list)
|
|
})
|
|
|
|
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;
|
|
})
|
|
|
|
// 方法
|
|
/**
|
|
* 初始默认选中一个id值。如果为空的话。
|
|
*/
|
|
const oninit = (): void => {
|
|
if (selectedsIds.value.length == 0 && _list.value.length > 0 && activeIndex.value == -1) {
|
|
activeIndex.value = 0
|
|
selectedsIds.value = [_list.value[activeIndex.value].id]
|
|
} else {
|
|
if (activeIndex.value == -1) {
|
|
let idx = -1;
|
|
for (let i = 0; i < _list.value.length; i++) {
|
|
let item = _list.value[i]
|
|
if (selectedsIds.value.includes(item.id)) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
activeIndex.value = idx == -1 ? 0 : idx
|
|
selectedsIds.value = selectedsIds.value.concat([_list.value[activeIndex.value].id])
|
|
} else {
|
|
selectedsIds.value = selectedsIds.value.concat([_list.value[activeIndex.value].id])
|
|
}
|
|
}
|
|
}
|
|
|
|
const getBoxHeight = (): void => {
|
|
clearTimeout(tid.value)
|
|
tid.value = setTimeout(function () {
|
|
uni.createSelectorQuery()
|
|
.in(proxy)
|
|
.select('.xSliderTree')
|
|
.boundingClientRect()
|
|
.exec((ret) => {
|
|
let node = ret[0] as NodeInfo
|
|
nrHeight.value = node.height! + 'px';
|
|
boxHeight.value = node.height!
|
|
})
|
|
}, 200);
|
|
}
|
|
|
|
/**
|
|
* 本下级选了几个
|
|
*/
|
|
const isSelectedNum = (item: SLIDER_TREE_ITEM): number => {
|
|
let ps = new Set(selectedsIds.value)
|
|
return getTreeSelectedNum(item.children, ps);
|
|
}
|
|
|
|
/**
|
|
* 当前是否选中
|
|
*/
|
|
const isSelected = (id: string): boolean => {
|
|
return selectedsIds.value.includes(id)
|
|
}
|
|
|
|
const sliderItemClick = (item: SLIDER_TREE_ITEM, index: number): void => {
|
|
if (item.disabled) return;
|
|
// 首栏不允许多选
|
|
activeIndex.value = index;
|
|
let ids = _list.value.map((el: SLIDER_TREE_ITEM): string => el.id)
|
|
let oks = selectedsIds.value.filter((el: string): boolean => !ids.includes(el))
|
|
oks.push(item.id as string)
|
|
selectedsIds.value = oks
|
|
}
|
|
|
|
const childrenClick = (id: string[], type: string): void => {
|
|
// 给当前级加上选中的子组级id
|
|
if (_multiple.value) {
|
|
selectedsIds.value = id
|
|
} else {
|
|
selectedsIds.value = id.concat([_list.value[activeIndex.value].id])
|
|
}
|
|
let idis = filterParentNode(_list.value, new Set(selectedsIds.value));
|
|
/**
|
|
* 更新当前的值,等同v-model
|
|
*/
|
|
emits('update:modelValue', idis)
|
|
if (type == 'click') {
|
|
emits('change', idis)
|
|
}
|
|
}
|
|
|
|
// 监听器
|
|
watch((): string[] => props.modelValue, (newValue: string[]) => {
|
|
selectedsIds.value = newValue;
|
|
oninit();
|
|
})
|
|
|
|
watch((): string => props.height, () => {
|
|
getBoxHeight();
|
|
})
|
|
|
|
// 生命周期
|
|
onMounted(() => {
|
|
selectedsIds.value = props.modelValue;
|
|
oninit();
|
|
getBoxHeight();
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
clearTimeout(tid.value)
|
|
})
|
|
|
|
defineExpose({
|
|
/** 获取当前选中的项 **/
|
|
getSelected: (): string[] => selectedsIds.value,
|
|
/** 设置选中的项 **/
|
|
setSelected: (ids: string[]): void => {
|
|
selectedsIds.value = ids;
|
|
oninit();
|
|
}
|
|
})
|
|
</script>
|
|
<template>
|
|
<view class="xSliderTree" :style="{width:_width,height:_height}">
|
|
<list-view
|
|
v-if="_list.length>0"
|
|
|
|
:style="{width:_sliderWidth,height:_height,backgroundColor:_sliderBgColor}"
|
|
>
|
|
<list-item
|
|
@click="sliderItemClick(item,index)"
|
|
v-for="(item,index) in _list"
|
|
:key="index"
|
|
class="xSliderTreeItemLeft"
|
|
:style="{
|
|
backgroundColor:isSelected(item.id)?_sliderContentBgColor:'transparent',
|
|
opacity:item.disabled?'0.5':1,
|
|
'border-left': `2px solid ${isSelected(item.id)?_activeTextColor:'transparent'}`
|
|
}"
|
|
>
|
|
<x-badge :dot="false" :count="isSelectedNum(item)">
|
|
<view><text class="xSliderTreeItemLeftText" :style="{fontSize:_fontSize,color:isSelected(item.id)? _activeTextColor : _textColor}">{{item.title}}</text></view>
|
|
</x-badge>
|
|
</list-item>
|
|
</list-view>
|
|
<view :style="{backgroundColor:_sliderContentBgColor,flex:1,height:'100%'}">
|
|
<x-slider-children
|
|
:font-size="_fontSize"
|
|
:height="nrHeight"
|
|
:boxHeight="boxHeight"
|
|
v-if="_isNodata" :nowSelecteds="selectedsIds" @itemClick="childrenClick" :multiple="_multiple" :sliderContentBgColor="_sliderContentBgColor" :itemActiveColor="_itemActiveColor" :itemTextColor="_itemTextColor" :index="0" :list="_list[activeIndex].children"></x-slider-children>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<style scoped>
|
|
.xSliderTree {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
|
|
}
|
|
.xSliderTreeItemLeft{
|
|
width:100%;
|
|
/* padding:0 24rpx; */
|
|
height: 50px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
.xSliderTreeItemLeftText{
|
|
text-align: center;
|
|
}
|
|
</style> |