123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- <template>
- <view class="tree-item">
- <view
- class="head"
- @click.stop="changeShow"
- style="padding-left: 20rpx"
- :class="{ active: isItemActive(item) }"
- >
- <text class="txt">{{ item[defaultProps.label] }}</text>
- <view
- class="left-icon"
- :class="show ? 'rt45' : ''"
- v-if="hasChildren"
- ></view>
- </view>
- <view class="content" v-if="shouldRecurse">
- <view
- v-for="(node, index) in nodes"
- :key="node._uid"
- class="tree-node-item"
- >
- <view
- class="head"
- @click.stop="toggleNode(index)"
- :style="{ paddingLeft: (node._level + 1) * 20 + 20 + 'rpx' }"
- :class="{ active: isItemActive(node) }"
- >
- <text class="txt">{{ node[defaultProps.label] }}</text>
- <view
- class="left-icon"
- :class="node._isExpanded ? 'rt45' : ''"
- v-if="getNodeChildren(node).length > 0"
- ></view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- export default {
- name: "TreeNode",
- componentName: "TreeNode",
- props: {
- item: {
- type: Object,
- default: function () {
- return {};
- },
- },
- defaultProps: {
- type: Object,
- default: function () {
- return {
- id: "id",
- children: "children",
- label: "label",
- };
- },
- },
- selectedNodeId: {
- type: [String, Number],
- default: null,
- },
- },
- computed: {
- hasChildren: function () {
- return (
- this.item[this.defaultProps.children] &&
- this.item[this.defaultProps.children].length > 0
- );
- },
- shouldRecurse: function () {
- return this.hasChildren && this.show;
- },
- },
- data: function () {
- return {
- show: false,
- nodes: [], // 扁平化后的节点列表
- };
- },
- methods: {
- // 判断节点是否被选中
- isItemActive: function (node) {
- if (!this.selectedNodeId || !node) return false;
- return node[this.defaultProps.id] === this.selectedNodeId;
- },
- // 获取节点的子节点
- getNodeChildren: function (node) {
- if (node && node[this.defaultProps.children]) {
- return node[this.defaultProps.children];
- }
- return [];
- },
- // 切换节点展开/折叠状态
- toggleNode: function (index) {
- var node = this.nodes[index];
- if (node) {
- // 触发节点点击事件
- this.$emit("node-click", node);
- // 如果有子节点,切换展开状态
- if (this.getNodeChildren(node).length > 0) {
- // 更新节点状态
- this.$set(node, "_isExpanded", !node._isExpanded);
- // 触发展开/折叠事件
- this.$emit(node._isExpanded ? "node-expand" : "node-collapse", node);
- // 重新构建扁平节点
- this.buildFlatNodes();
- }
- }
- },
- // 根节点的展开/折叠
- changeShow: function () {
- // 触发根节点点击事件
- this.$emit("node-click", this.item);
- if (this.hasChildren) {
- this.show = !this.show;
- // 触发展开/折叠事件
- this.$emit(this.show ? "node-expand" : "node-collapse", this.item);
- // 当展开时,构建扁平节点列表
- if (this.show) {
- this.buildFlatNodes();
- } else {
- this.nodes = [];
- }
- }
- },
- // 构建扁平的节点列表
- buildFlatNodes: function () {
- var result = [];
- var children = this.item[this.defaultProps.children] || [];
- // 添加第一层子节点
- for (var i = 0; i < children.length; i++) {
- var node = children[i];
- if (!node._uid) {
- this.$set(node, "_uid", this.generateUID());
- }
- if (typeof node._level === "undefined") {
- this.$set(node, "_level", 0);
- }
- if (typeof node._isExpanded === "undefined") {
- this.$set(node, "_isExpanded", false);
- }
- result.push(node);
- // 如果节点已展开且有子节点,递归添加子节点
- if (node._isExpanded) {
- this.addChildrenToResult(node, result, 1);
- }
- }
- this.nodes = result;
- },
- // 递归添加子节点到结果数组
- addChildrenToResult: function (parentNode, result, level) {
- var children = this.getNodeChildren(parentNode);
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- if (!child._uid) {
- this.$set(child, "_uid", this.generateUID());
- }
- this.$set(child, "_level", level);
- if (typeof child._isExpanded === "undefined") {
- this.$set(child, "_isExpanded", false);
- }
- result.push(child);
- // 如果子节点已展开且有子节点,继续递归
- if (child._isExpanded && this.getNodeChildren(child).length > 0) {
- this.addChildrenToResult(child, result, level + 1);
- }
- }
- },
- // 生成唯一ID
- generateUID: function () {
- return (
- Math.random().toString(36).substring(2, 15) +
- Math.random().toString(36).substring(2, 15)
- );
- },
- },
- };
- </script>
- <style scoped lang="scss">
- @mixin animate2 {
- -moz-transition: all 0.2s linear;
- -webkit-transition: all 0.2s linear;
- -o-transition: all 0.2s linear;
- -ms-transition: all 0.2s linear;
- transition: all 0.2s linear;
- }
- .tree-item {
- .head {
- display: flex;
- align-items: center;
- line-height: 60rpx;
- min-height: 60rpx;
- position: relative;
- padding-right: 50rpx;
- &.active {
- background-color: #fff;
- }
- .txt {
- flex: 1;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
- height: 84rpx;
- line-height: 84rpx;
- color: #666;
- font-family: "PingFang SC";
- font-size: 28rpx;
- font-weight: 400;
- }
- }
- .left-icon {
- width: 30rpx;
- height: 30rpx;
- flex-shrink: 0;
- position: absolute;
- right: 10rpx;
- top: 50%;
- transform: translateY(-50%) rotate(-90deg);
- -ms-transform: translateY(-50%) rotate(-90deg);
- -moz-transform: translateY(-50%) rotate(-90deg);
- -webkit-transform: translateY(-50%) rotate(-90deg);
- -o-transform: translateY(-50%) rotate(-90deg);
- @include animate2;
- &::before {
- content: "";
- position: absolute;
- top: 50%;
- left: 50%;
- margin-top: -6rpx;
- margin-left: -6rpx;
- width: 0;
- height: 0;
- border-left: 6rpx solid transparent;
- border-right: 6rpx solid transparent;
- border-top: 12rpx solid #999;
- }
- &.rt45 {
- transform: translateY(-50%) rotate(0deg);
- -ms-transform: translateY(-50%) rotate(0deg);
- -moz-transform: translateY(-50%) rotate(0deg);
- -webkit-transform: translateY(-50%) rotate(0deg);
- -o-transform: translateY(-50%) rotate(0deg);
- }
- }
- .content {
- width: 100%;
- }
- .tree-node-item {
- width: 100%;
- }
- }
- </style>
|