背景
这是效果图,pc因为是element plus,有树形组件可以直接使用,小程序使用的是nut-ui,可用组件不多,只能手写出树形的表格了
因为层级是不确定的,多的可达8层,所以层级数据是动态获取的,接口给的数据是一次只给一层,拿第一层的id,去获取第二层的数据,依次类推
jsx
当前小程序使用的是taro Vue3框架,可直接使用jsx语法,新建个js文件,然后引入到vue的 组件中
https://taro-docs.jd.com/docs/vue3
``
import { View } from "@tarojs/components";
import Taro from '@tarojs/taro'
import "./tTree.scss";
import { defineComponent, reactive, onMounted, watch } from "vue";
export default defineComponent({
props: {
params: Object,
},
setup(props) {
const state = reactive({
});
return () => {
return (
<View className="tree">
</View>
);
};
},
});
vue组件中引用js文件
<t-tree :params="state.params" />
import TTree from
'@/pages/index/components/tTree/tTree.js'
树形表格
<View className="table">
<View className="tableTitle">
<View
className="tableTitleItem"
style={{ textAlign: "left", marginLeft: "5px" }}
>
区域
</View>
<View className="tableTitleItem">首次流向</View>
<View className="tableTitleItem">自然流向</View>
<View className="tableTitleItem">临采</View>
<View className="tableTitleItem"> 正式采购</View>
</View>
<View className="tableAll" >
{
state.treeLongData.map((item) => (
<View className="tableContent">{treeNode(item, 0)}</View>
))}
<View className="tableContentTetx">*统计为机构数</View>
</View>
</View>
这里是return结构代码,表头(tableTitle)和内容区域(tableAll)是分开的
第一次请求接口
初始化时候,是第一次请求接口,拿到是第一层的数据,这里把第一个层级,和下面所有层级,拿接口的数据逻辑,分开来写了
const getStatisticsHcd = () => {
Taro.showLoading({
title: "加载中",
});
let params = {
deptCode:'',
}
statisticsHcd(params).then((res) => {
const index = res.rows.findIndex((item) => item.deptCode === "-1");
if (index !== -1) {
const removedItem = res.rows.splice(index, 1);
res.rows.push(removedItem[0]);
}
// 这里把-1单独过滤出来,是因为-1的是代表合计的数据,并且需要展示在最下面,所以把他push到最后了
state.treeLongData = res.rows;
Taro.hideLoading();
}).catch(() => {
Taro.hideLoading();
})
};
递归树形节点
const treeNode = (item, _deep) => {
// deep用于样式上一个向后退的展示,下一层级在样式上比上一层稍微靠后
let deep = _deep + 1;
return (
<>
<View
// item.deptCode === "-1"把为-1的过滤出来,改变颜色
className={`tableContentItem ${
item.deptCode === "-1" ? "color1" : ""
} ` }
>
<View
// 每一层渲染时候表头第一项都会有个后退的样式
className="tableItem"
style={{ textIndent: `${deep * 5}px`, textAlign: "left" }}
>
{item.deptName === null ? "-" : item.deptName}
</View>
<View
// !item.hasChildren 表示是最后一个节点了,最后一个节点需要高亮并且是有数据的情况下
className={`tableItem ${
!item.hasChildren && item.first ? "color2" : ""
} ` }>
{item.first === null ? "-" : item.first}
</View>
<View className={`tableItem ${
!item.hasChildren && item.natural ? "color2" : ""
} ` }>
{item.natural === null ? "-" : item.natural}
</View>
<View className={`tableItem ${
!item.hasChildren && item.temporary ? "color2" : ""
} ` } >
{item.temporary === null ? "-" : item.temporary}
</View>
<View className={`tableItem ${
!item.hasChildren && item.formal ? "color2" : ""
} ` } >
{item.formal === null ? "-" : item.formal}
</View>
{item.hasChildren ? (
// 最后面的 展开收缩按钮展示
<View className="tableItemIcon">{nodeButton(item)}</View>
) : (
""
)}
</View>
{/* 有无children,进行递归 clicked 主要代表展示收缩*/}
{item.children &&
!item.clicked &&
item.children.map((child) => {
return treeNode(child, deep);
})}
</>
);
};
展开收缩按钮展示
const nodeButton = (item) => {
if (item.clicked) {
return (
// 这里把宽度设置为100%,是为了点击行的时候也可以执行事件,毕竟按钮太小,点击起来费劲
// 展开下一层的图标
<View style={{width: '100%', height: '38px'}} onClick={() => getTreeDate(item)}>
<Image
src={down}
class="tableItemImg"
/>
</View>
);
} else if (item.children && item.children.length && !item.clicked) {
// 收缩的图标
return (
<View style={{width: '100%', height: '38px'}} onClick={() => hideTreeDate(item.deptCode)}>
<Image
src={up}
class="tableItemImg"
/>
</View>
);
} else if (item.hasChildren) {
// 展开下一层的图标
return (
<View style={{width: '100%', height: '38px'}} onClick={() => getTreeDate(item)}>
<Image
src={down}
class="tableItemImg"
/>
</View>
);
}
};
第二层以及后面所有层的接口展示
这里就是点击展示时候执行的
const getTreeDate = (i) => {
Taro.showLoading({
title: "加载中",
});
let params = {
deptCode: i.deptCode,
};
statisticsHcd(params).then((res) => {
// 这里-1是合计,不需要,
const rows = res.rows.filter((item) => item.deptCode !== "-1");
const newRows =rows;
if (i.clicked && i.children && i.children.length) {
hideTree(state.treeLongData, i.deptCode, false);
} else {
updateTreeData(state.treeLongData, i.deptCode, newRows);
}
Taro.hideLoading();
}).catch(() => {
Taro.hideLoading();
})
};
-----------------------------
递归把childer塞进去
const updateTreeData = (nodes, deptCode, newChildren) => {
nodes.forEach((node) => {
if (node.deptCode === deptCode) {
node.children = newChildren;
} else if (node.children) {
updateTreeData(node.children, deptCode, newChildren);
}
});
};
收缩
const hideTreeDate = (code) => {
hideTree(state.treeLongData, code, true);
};
--------------------------------------
const hideTree = (tree, code, type) => {
// clicked 点击的当前节点数据里增加属性,设置为true,再节点渲染时候,节点里clicked属性为true的是不会再往下进行渲染的
// {item.children &&
// !item.clicked &&
// item.children.map((child) => {
// return treeNode(child, deep);
// })}
tree.forEach((treeItem) => {
if (treeItem.deptCode === code) {
treeItem.clicked = type;
} else if (treeItem.children && treeItem.children.length > 0) {
hideTree(treeItem.children, code, type);
}
});
};
收缩起来再次展开
这时候点击的时机,是需要展示,下一层的节点,也是之前收缩被隐藏的节点,所以调用还是
getTreeDate 方法
const getTreeDate = (i) => {
Taro.showLoading({
title: "加载中",
});
let params = {
deptCode: i.deptCode,
};
statisticsHcd(params).then((res) => {
const rows = res.rows.filter((item) => item.deptCode !== "-1");
const newRows =rows;
// 把之前收缩设置clicked true的更改成false,
if (i.clicked && i.children && i.children.length) {
hideTree(state.treeLongData, i.deptCode, false);
} else {
updateTreeData(state.treeLongData, i.deptCode, newRows);
}
Taro.hideLoading();
}).catch(() => {
Taro.hideLoading();
})
};
getTreeDate 这个方法可以优化下,再收缩展示时没必要再走次接口,把 if (i.clicked && i.children && i.children.length) 判断提到最前面
树形结构也就只能用递归实现了,结构上设计的不好,有些样式需求做不了