当前位置: 首页 > news >正文

【uni-app】树形结构数据选择框

该篇文章的写法采用的是v3写法,如果需要v2写法的 可以把代码复制进去让AI处理一下

效果预览:

在这里插入图片描述

组件图片分享:

在这里插入图片描述

主页面示例:

<template><view><view>{{ selectedTypeName || '请选择' }}</view><button @click="showDeviceTypeModal = true">选择类型</button><!-- 自定义模态选择器 --><view class="type-modal" @click="closeModal" v-if="showDeviceTypeModal"><view class="type-content" @click.stop><view class="type-header"><text class="type-title">选择类型</text><text class="type-close" @click="closeModal">×</text></view><scroll-view class="type-scroll" scroll-y="true" show-scrollbar="true"><view class="type-list"><view v-for="item in dataSource" :key="item.value"><neo-tree-list-item :paramData="item" title="label" @tapText="handleItem" /></view></view></scroll-view></view></view></view>
</template><script setup>
import { ref } from "vue";
import NeoTreeListItem from '@/components/neo-tree-list-item/neo-tree-list-item.vue';// 假数据示例
const dataSource = [{text: '设备类型1',value: '1',children: [{text: '设备类型1-1',value: '1-1',children: [{text: '设备类型1-1-1',value: '1-1-1',children: [{text: '设备类型1-1-1-1',value: '1-1-1-1',},{text: '设备类型1-1-1-2',value: '1-1-1-2',},{text: '设备类型1-1-1-3',}]},{text: '设备类型1-1-2',value: '1-1-2',},{text: '设备类型1-1-3',value: '1-1-3',}]},{text: '设备类型1-2',value: '1-2',},{text: '设备类型1-3',value: '1-3',},{text: '设备类型1-4',value: '1-4',}]},{text: '设备类型2',value: '2',},{text: '设备类型3',value: '3',},{text: '设备类型4',value: '4',},
]const showDeviceTypeModal = ref(false);
const closeModal = () => {showDeviceTypeModal.value = false;
};const selectedTypeName = ref('');
const handleItem = (item) => {console.log(item);selectedTypeName.value =item.textcloseModal();
};</script><style lang="scss" scoped>.type-modal {position: fixed;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(0, 0, 0, 0.6);z-index: 999;display: flex;justify-content: center;align-items: center;.type-content {width: 90%;max-height: 80%;background-color: #fff;border-radius: 16rpx;overflow: hidden;.type-header {display: flex;justify-content: space-between;align-items: center;padding: 20rpx 30rpx;border-bottom: 2rpx solid #f0f0f0;.type-title {font-size: 32rpx;font-weight: bold;}.type-close {font-size: 40rpx;color: #999;}}.type-scroll {max-height: 50vh;.type-list {padding-bottom: 20rpx;.type-item {display: flex;justify-content: space-between;align-items: center;padding: 20rpx 30rpx;border-bottom: 2rpx solid #f0f0f0;&.disabled {color: #ccc;}&.selected {color: #007fff;}.item-text {font-size: 28rpx;}.check-icon {margin-left: 20rpx;}}}}.type-footer {padding: 20rpx 30rpx;display: flex;justify-content: center;.confirm-btn {width: 100%;background-color: #007fff;color: #fff;border-radius: 10rpx;}}}
}</style>

组件页面:

<template><view class="col-item" :class="{ 'col-item-bot': localShow }"><block v-if="paramData"><view class="col-item-title"><view class="item-box" :class="{ 'ch-item': currentLayer === 1 }"@click="handleItem(paramData)"><imagev-if="hasChildren"@click.stop="tapItemOne(paramData)":class="localShow ? 'arrow-down-css' : 'arrow-right-css'"src="./image/arrow.png"class="arrow-icon"/><view class="item-box-left"><view class="left-images" v-show="currentLayer === 1"></view><view>{{ paramData[title] || paramData.text }}</view></view></view></view><view v-if="hasChildren && shouldRenderChildren" v-show="localShow"class="children-container"><view v-for="item in paramData[children]" :key="getItemKey(item)"><neo-tree-list-item@parentEmit="parentEmit":parentData="paramData":title="title":layer="currentLayer + 1":paramData="item"@tapText="onTapText"@tapTitle="onTapTitle"/></view></view></block></view>
</template><script setup>
import { ref, computed, watch } from 'vue';const props = defineProps({paramData: {type: Object,default: undefined},parentData: {type: Object,default: undefined},title: {type: String,default: 'text'},children: {type: String,default: 'children'}
});const emit = defineEmits(['tapText', 'tapTitle', 'parentEmit', 'update:show']);const currentLayer = ref(0);
const hasRenderedChildren = ref(false);
const localShow = ref(props.paramData?.show || false);const hasChildren = computed(() => {return props.paramData?.[props.children]?.length > 0;
});const shouldRenderChildren = computed(() => {if (hasRenderedChildren.value) return true;if (localShow.value) {hasRenderedChildren.value = true;return true;}return false;
});watch(() => props.paramData?.show,(newVal) => {localShow.value = newVal || false;if (newVal && !hasRenderedChildren.value) {hasRenderedChildren.value = true;}},{ immediate: true }
);const getItemKey = (item) => {return item.value || item.id || item.text || JSON.stringify(item);
};const tapItemOne = (item) => {if (!hasChildren.value) {emit('tapTitle', item);return;}// 基于当前 localShow 状态来切换,而不是 item.showconst newShowValue = !localShow.value;localShow.value = newShowValue;// 通知父组件更新状态emit('update:show', {item: item,show: newShowValue});if (newShowValue && item.created === undefined) {item.created = true;hasRenderedChildren.value = true;}
};// 其他方法保持不变
const handleItem = (item) => {emit('tapText', item);
};const onTapText = (item) => {emit('tapText', item);
};const onTapTitle = (item) => {emit('tapTitle', item);
};const parentEmit = () => {if (props.parentData) {emit('parentEmit');}
};const recursionChecked = (item, checked) => {if (!item[props.children]) return;item[props.children].forEach(child => {child.checked = checked;recursionChecked(child, checked);});
};watch(() => props.paramData,() => {},{ deep: true }
);
</script><style scoped lang="scss">
.col-item {background: #ffffff;.col-item-title {display: flex;justify-content: flex-start;}.left-image {margin: 16rpx 0rpx 16rpx 32rpx;padding: 12rpx;display: flex;align-items: center;justify-content: center;background: rgba(0, 127, 255, 0.12);border-radius: 12rpx;.img {width: 60rpx;}}.item-box {height: 80rpx;display: flex;align-items: center;justify-content: flex-start;width: 100%;padding: 0 32rpx;border-bottom: 2rpx solid rgba(126, 134, 142, 0.16);.item-box-left {display: flex;align-items: center;justify-content: flex-start;line-height: 40rpx;}.left-images {margin: 16rpx 0rpx;width: 40rpx;height: 40rpx;display: flex;align-items: center;justify-content: center;border-radius: 12rpx;margin-right: 24rpx;.img {width: 60rpx;}}}.ch-item {border-bottom: 0;box-shadow: 124rpx 2rpx 0rpx rgba(126, 134, 142, 0.16);}
}.col-item-bot {margin-bottom: 24rpx;
}.arrow-down-css,
.arrow-right-css {width: 30rpx;height: 30rpx;margin-right: 1rpx;transition: transform 0.2s ease;
}.arrow-down-css {transform: rotate(90deg);
}.arrow-right-css {transform: rotate(0deg);
}.children-container {padding-left: 60rpx; // 图标宽度+间距,确保所有子级对齐
}
</style>
http://www.dtcms.com/a/406236.html

相关文章:

  • 视频解析网站甜品蛋糕网站建设策划书
  • PostgreSQL 中序列(Sequence)的详细用法
  • 超低延迟与高并发保障:互联网直播点播平台EasyDSS如何成为企业级现场直播的“技术底座”?
  • 一种个性化认知型人形机器人端到端的架构设计
  • Frp内网穿透v0.64.0
  • 9.25交作业
  • 【原理与应用】3-flink安装与部署
  • 网站经营性备案难不难良品铺子网络营销策划书
  • 永磁同步电机驱动控制系统设计(论文+仿真)
  • Cherry Studio+Ollama+大模型+向量模型,实现RAG私有知识库。智能体实现EXCEL转化为一个报表图表
  • Ansible Playbook 入门指南:从基础到实战
  • 什么是提示词追问?
  • 【MD编辑器Typora】Typora最新 V1.12.1版:轻量级 Markdown 编辑器详细图文下载安装使用指南 【办公学习神器之MD文本编辑器】
  • 内外外贸购物网站建设seo基础优化包括哪些内容
  • 冰雪守护者:输电线路图像识别覆冰监测系统为电网保驾护航
  • MCU的闪存(FLASH)存储器的接口寄存器
  • 软件毕设代做网站阿里云建设网站的流程
  • 第12篇|[特殊字符] Freqtrade 交易所接入全解:API、WebSocket、限频配置详解
  • k8s etcd 运行错误 failed to find plugin “flannel“ in path [/usr/lib/cni]
  • 【LeetCode - 每日1题】计算三角形最小路径和
  • 信息安全工程师考点-安全体系结构
  • 小说网站制作开源山东网站开发
  • 医院网站建设的目的大学跳蚤市场网站建设
  • Python SQLite模块:轻量级数据库的实战指南
  • 学习HAL库STM32F103C8T6(SPI、门禁密码实验)
  • 2025年DevOps平台演进方向:智能化、平台工程与价值流管理
  • 数据采集(爬虫)
  • 学习Java第二十二天——苍穹外卖Day10-all
  • C语言底层学习(3.指针、函数与数组)(超详细)
  • 基于XTDIC-SPARK三维高速测量系统的电子产品跌落测试研究