【vue3+vant】移动端 - 部门树下拉选择组件 DeptTreeSelect 开发
目录
- 效果展示
- 代码
- 父组件
- 子组件 DeptTreeSelect
效果展示
代码
父组件
父组件使用子组件
<template><div class="supervision-report"><van-fieldv-model="infoView.ssbm"name="ssbm"label="督察对象部门"placeholder="请选择督查对象部门"readonlyright-icon="arrow-down"required:rules="[{ required: true, message: '请选择督察对象部门' }]"@click="showPickerHandle('ssbm')"/><!-- 省略无关代码 --><van-popupv-model:show="isShowPicker"position="bottom":close-on-click-overlay="curColumnsKey !== 'ssbm'"><DetpTreeSelect:init-dept-tree="columnsObj.ssbm"@close="isShowPicker = false"@setValue="setSsbm"/></van-popup></div>
</template><script>
import { ref, onMounted } from 'vue'import { getDeptTree } from '@/api/global-dict-service'export default {setup() {const info = reactive({ssbm: ''})const infoView = reactive({ssbm: ''})const isShowPicker = ref(false)const curColumnsKey = ref('')const columnsObj = reactive({ssbm: []})onMounted(() => {getDept()})const getDept = () => {getDeptTree({}).then(res => {columnsObj.ssbm = res.data || [] // 数据格式在子组件中有})}const showPickerHandle = key => {curColumnsKey.value = keyif (['fssj'].includes(key)) {const curTime = info.fssjcurTime ? (currentDate.value = new Date(curTime)) : new Date()}isShowPicker.value = true}const setSsbm = val => {console.log('val----打印', val)info.ssbm = val.dwdminfoView.ssbm = val.dwjc}return {info,infoView,showPickerHandle,columnsObj,isShowPicker,curColumnsKey,setSsbm}}
}
</script><style lang="less" scoped>
@bgc: #f3f4f6;.supervision-report {height: 100vh;background-color: #fff;.van-form {overflow: auto;}:deep(.van-cell) {@lineHeight: 40px;padding: 10px;&:nth-child(-n + 8) {label {line-height: @lineHeight;}}input {height: @lineHeight;}}:deep(.van-field__body) {background-color: @bgc;border-radius: 5px;padding: 0 10px;}.iconfont,:deep(.van-field__right-icon .van-icon) {font-size: 14px;}.field_block {display: block;}:deep(.van-uploader) {width: 100%;.van-uploader__input-wrapper {width: 100%;border-top: 1px dashed #ccc;.upload_btn {height: 100px;width: 100%;border-radius: 5px;display: flex;flex-direction: column;align-items: center;justify-content: center;color: #9ca3af;font-size: 14px;}}}.upload_tips {margin: 0 20px 10px;color: #9ca3af;word-break: break-all;font-size: 14px;}.btns {display: flex;justify-content: space-between;margin: 0 20px 20px;.van-button {flex: 1;border-radius: 5px;&:first-child {margin-right: 10px;}}}.dialog_father {width: 100%;height: 100vh;position: fixed;top: 0;left: 0;background-color: #423e3e39;display: flex;align-items: center;justify-content: center;.dialog {background-color: #fff;width: 95%;padding: 10px;border-radius: 10px;box-sizing: border-box;&_header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;font-size: 18px;.title {}.close {padding: 5px;font-size: 22px;color: #9ca3af;}}&_body {.TMap {width: 100%;height: 70vh;}}&_footer {display: flex;gap: 10px;margin-top: 10px;.van-button {flex: 1;}}}}
}
</style>
子组件 DeptTreeSelect
@/components/DeptTreeSelect.vue
<!-- 部门树下拉选择组件 -->
<template><div class="area-picker"><div class="btns"><span @click="reset">取消</span><span class="blue" @click="handleConfirm">确定</span></div><!-- 已选择的部门数据 --><div class="header"><spanv-for="(selectedArea, index) in selectedAreas":key="selectedArea.data.dwdm"@click="toggleArea(selectedArea, index)">{{ selectedArea.data?.dwjc }}<i v-if="index < selectedAreas.length - 1" class="arrow"> > </i></span></div><!-- 当前可选择的部门数据 --><div class="body"><divv-for="area in renderAreaList":key="area.data.dwdm"class="body_item"@click="selectDeptFun(area)">{{ area.data?.dwjc }}<span v-if="selectedAreas.includes(area)" class="blue font_weight">✓</span></div></div></div>
</template><script setup name="DeptTreeSelect">
import { ref } from 'vue'
import { Toast } from 'vant'// initDeptTree 为父组件传的部门树数据
const { initDeptTree } = defineProps({initDeptTree: {type: Array,default: () => [{data: {dwdm: '330000000000',dwmc: '台州市公安局',dwjc: '台州市局',sjdwdm: null},childrenNode: [{data: {dwdm: '33100000011X',dwmc: '仙居县局',dwjc: '台州市局-仙居县局',sjdwdm: '330000000000'},childrenNode: []},{data: {dwdm: '33100000012X',dwmc: '天台县局',dwjc: '台州市局-天台县局',sjdwdm: '330000000000'},childrenNode: []},{data: {dwdm: '33100000013X',dwmc: '三门县局',dwjc: '台州市局-三门县局',sjdwdm: '330000000000'},childrenNode: []},{data: {dwdm: '331000290000',dwmc: '市局各部门',dwjc: '台州市局-市局各部门',sjdwdm: '330000000000'},childrenNode: []}]},{data: {dwdm: '335401000010',dwmc: '高一大队',dwjc: '高一大队',sjdwdm: null},childrenNode: [{data: {dwdm: '33540100001A',dwmc: '一中队',dwjc: '高一大队-一中队',sjdwdm: '335401000010'},childrenNode: []},{data: {dwdm: '33540100001B',dwmc: ' 二中队',dwjc: '高一大队- 二中队',sjdwdm: '335401000010'},childrenNode: []},{data: {dwdm: '33540100001C',dwmc: '三中队',dwjc: '高一大队-三中队',sjdwdm: '335401000010'},childrenNode: []},{data: {dwdm: '33540100001D',dwmc: '四中队',dwjc: '高一大队-四中队',sjdwdm: '335401000010'},childrenNode: []}]}]}
})
const emit = defineEmits(['setValue', 'close'])const renderAreaList = ref([...initDeptTree]) // 存储渲染的区域数据
const selectedAreas = ref([]) // 已选择的部门数据 -- header 部分渲染的数据// 点击 header 部分切换已选择部门
const toggleArea = (area, toggleClickIndex) => {renderAreaList.value =toggleClickIndex === 0? [...initDeptTree]: selectedAreas.value[toggleClickIndex - 1].childrenNodeselectedAreas.value = selectedAreas.value.slice(0, toggleClickIndex)
}// 选中部门时处理
const selectDeptFun = area => {if (area.childrenNode.length > 0) {renderAreaList.value = area.childrenNode} else {const index = selectedAreas.value.findIndex(item => item.data.sjdwdm === area.data.sjdwdm)if (index !== -1) {selectedAreas.value.splice(index, 1)}}selectedAreas.value.push(area)
}// 重置部门数据
const reset = () => {selectedAreas.value = []renderAreaList.value = [...initDeptTree]emit('close')
}// 确定选择部门
const handleConfirm = () => {if (!selectedAreas.value.length) {Toast('请选择部门')return}const value = selectedAreas.value[selectedAreas.value.length - 1].dataemit('setValue', value)reset()
}
</script><style scoped lang="less">
.area-picker {background-color: #e8e7ea;max-height: 50vh;min-height: 30vh;// overflow: auto;display: flex;flex-direction: column;.blue {color: #3970e3;}.font_weight {font-weight: bold;}.btns {display: flex;justify-content: space-between;padding: 10px;background-color: #fff;border-bottom: 1px solid #ccc;font-size: 18px;}.header {background-color: #fff;margin-bottom: 10px;padding: 5px 10px;font-size: 16px;.arrow {margin: 0 5px;}}.body {flex: 1;overflow: auto;padding: 0 8px;background-color: #fff;font-size: 16px;&_item {display: flex;padding: 8px;justify-content: space-between;border-bottom: 1px solid #eee;}}
}
</style>