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

【vue3+vant】移动端 - 部门树下拉选择组件 DeptTreeSelect 开发

目录

  • 效果展示
  • 代码
    • 父组件
    • 子组件 DeptTreeSelect

效果展示

在这里插入图片描述

代码

父组件

父组件使用子组件

<template>
  <div class="supervision-report">
    <van-field
      v-model="infoView.ssbm"
      name="ssbm"
      label="督察对象部门"
      placeholder="请选择督查对象部门"
      readonly
      right-icon="arrow-down"
      required
      :rules="[{ required: true, message: '请选择督察对象部门' }]"
      @click="showPickerHandle('ssbm')"
    />
    <!-- 省略无关代码 -->
    <van-popup
      v-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 = key

      if (['fssj'].includes(key)) {
        const curTime = info.fssj
        curTime ? (currentDate.value = new Date(curTime)) : new Date()
      }
      isShowPicker.value = true
    }

    const setSsbm = val => {
      console.log('val----打印', val)
      info.ssbm = val.dwdm
      infoView.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">
      <span
        v-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">
      <div
        v-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].childrenNode

  selectedAreas.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].data
  emit('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>


http://www.kler.cn/a/592584.html

相关文章:

  • rip 协议详细介绍
  • vue 中常用操作数组的方法
  • 【Python 的发展历史】
  • 【2025】基于Springboot + vue实现的毕业设计选题系统
  • 优选算法系列(2.滑动窗口_下)
  • C语言每日一练——day_12(最后一天)
  • 【江协科技STM32】软件I2C协议层读写MPU6050驱动层
  • 动态代理示例解析
  • 3.19学习总结
  • 递归分治法格雷码
  • 刷题练习笔记
  • 基于SpringBoot + Vue 的图书馆座位预约系统
  • 红日靶场(二)——个人笔记
  • HarmonyOS开发,console.log和hilog的区别,如何选择使用?
  • 两矩阵相乘(点乘和乘的区别)
  • Matrix-breakout-2-morpheus靶机实战攻略
  • 算法、数据结构、计算机网络,编译原理,操作系统常考题
  • Node.js系列(4)--微服务架构实践
  • 数据结构之链表(双链表)
  • 如何在 GoLand 中设置默认项目文件夹