最后实现效果
如图所示,这个弹窗的数据有三级,其中一级很好做,难在二级和三级,需要控制三级的样式相对于二级动态展示,点击二级,三级的div盒子宽度没办法显示一整行,只能展示那一列。所以最后实现效果需要根据二级进行绝对定位,由于绝对定位,三级盒子不占位,位置根据当前的选中行来计算,当前选中行的下一行设置一个margin top
,然后margintop
的高度动态获取。
主要代码
<div class="right">
<div class="right_box">
<div v-for="array, index in convertList" :key="`array-${index}`"
:class="['row', currentRowIndex === index && isShowThree ? 'row_space' : '']">
<div class="text_box" v-for="item in array" :key="item.id">
<div class="text_icon flex" @click.stop="twoClick(item, index)"
:class="{ 'marginBottom': currentItemId === item.id }">
<img class="position_icon pointer" src="@/assets/image/position_jian.png" width="15" height="14" alt=""
v-if="currentItemId === item.id && isShowThree">
<img class="position_icon pointer" src="@/assets/image/position_jia.png" width="15" height="14" alt=""
v-else>
<div class="ellipsis" :class="{ 'relative_box': currentItemId === item.id }">{
{
item.displayName }}
</div>
</div>
</div>
</div>
<!-- 第三级的高度,用第三级item的行数*行高来计算,动态替换参考上面 -->
<div v-if="currentRowIndex >= 0 && isShowThree" class="three_tree"
:style="{ top: `${currentRowIndex * 40 + 60}px` }">
<div>
<el-row :gutter="20">
<el-col class="three_item" v-for="item in threeItemList" :key="item.id" @click="threeClick(item)"
:span="6">
<div class="pointer" :class="{ 'three_selected': currentItemThree === item.id }">
{
{
item.displayName }}
</div>
</el-col>
</el-row>
</div>
</div>
</div>
</div>
最重要的一个步骤就是将一维数组转化成二维数组,因为用一维数组直接循环盒子,得到的样式只能是按列来的,所以需要将数组处理成一行,就是四个(index % 4 === 0
)
/**
* 一维数组转换成展示的二维数组
*/
const convertList = computed(() => {
if (twoItemList.value && twoItemList.value.length) {
return twoItemList.value.reduce((acc, cur, index) => {
if (index % 4 === 0) {
// 每行展示4个
acc.push(twoItemList.value.slice(index, index + 4))
}
return acc
}, [])
} else {
return []
}
})
所有代码包括样式
<template>
<!-- 职位类型 -->
<div class="dialog">
<el-dialog :model-value="positionShow" destroy-on-close width="910" v-bind="$attrs" title="请选择职类" @close="closeBtn">
<template #header>
<div class="flex header_box">
<div class="header_title">请选择职类</div>
<el-select v-model="positionName" placeholder="请输入职位名称" clearable filterable remote :teleported="false"
:remote-method="remoteMethod" :suffix-icon="Search" @change="searchChange" @keyup.enter="getSearch()">
<el-option style="padding: 0;width: 100%;" v-for="item in positionSearchData" :key="item.id"
:label="item.displayName" :value="item.id">
<span style="float: left; font-size: 15px;margin-top:-6px;">{
{
item.displayName }}</span>
<span style="float: left;display:block;position:absolute;margin-top:15px; font-size: 14px;color: #999999;">
{
{
item.parentDisplayName }}
</span>
</el-option>
</el-select>
</div>
</template>
<div class="container">
<div class="left">
<div v-for="(item, index) in positionTypeData" :key="index"
:class="['item', 'ellipsis', item.id === parentId && 'actived']" @click="oneClick(item)">
{
{
item.displayName }}
</div>
</div>
<div class="right">
<div class="right_box">
<div v-for="array, index in convertList" :key="`array-${index}`"
:class="['row', currentRowIndex === index && isShowThree ? 'row_space' : '']">
<div class="text_box" v-for="item in array" :key="item.id">
<div class="text_icon flex" @click.stop="twoClick(item, index)"
:class="{ 'marginBottom': currentItemId === item.id }">
<img class="position_icon pointer" src="@/assets/image/position_jian.png" width="15" height="14" alt=""
v-if="currentItemId === item.id && isShowThree">
<img class="position_icon pointer" src="@/assets/image/position_jia.png" width="15" height="14" alt=""
v-else>
<div class="ellipsis" :class="{ 'relative_box': currentItemId === item.id }">{
{
item.displayName }}
</div>
</div>
</div>
</div>
<!-- 第三级的高度,用第三级item的行数*行高来计算,动态替换参考上面 -->
<div v-if="currentRowIndex >= 0 && isShowThree" class="three_tree"
:style="{ top: `${currentRowIndex * 40 + 60}px` }">
<div>
<el-row :gutter="20">
<el-col class="three_item" v-for="item in threeItemList" :key="item.id" @click="threeClick(item)"
:span="6">
<div class="pointer" :class="{ 'three_selected': currentItemThree === item.id }">
{
{
item.displayName }}
</div>
</el-col>
</el-row>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import {
computed, ref, toRefs, watch } from 'vue'
import {
useRequest } from 'vue-request'
import {
Search } from '@element-plus/icons-vue'
import {
getPositionCategoryTree, positionSearch } from '@/apis/company/job'
const props = defineProps({
positionShow: {
type: Boolean,
default: () => {
return false }
},
positionId: {
type: String,
default: () => {
return null }
}
})
const {
positionShow, positionId } = toRefs(props)
const emit = defineEmits(['positionChange', 'update:positionShow'])
// 二三级数据处理
const currentItemId = ref(-1) // 选中ItemID
const currentRowIndex = ref(-1) // 选中项目所在行
const currentItemThree = ref() // 选中的第三级item
const twoItemList = ref([]) // 右侧二级数据
const threeItemList = ref([]) // 三级数据
const isShowThree = ref(false) // 是否展示三级
/**
* 一维数组转换成展示的二维数组
*/
const convertList = computed(() => {
if (twoItemList.value && twoItemList.value.length) {
return twoItemList.value.reduce((acc, cur, index) => {
if (index % 4 === 0) {
// 每行展示4个
acc.push(twoItemList.value.slice(index, index + 4))
}
return acc
}, [])
} else {
return []
}
})
const oneClick = (item) => {
isShowThree.value = false
parentId.value = item.id
if (item.children && item.children.length > 0) {
twoItemList.value = item.children
}
}
const twoClick = (item, index) => {
if (currentRowIndex.value === index && currentItemId.value === item.id) {
isShowThree.value = !isShowThree.value
} else {
isShowThree.value = true
}
currentItemId.value = item.id
currentRowIndex.value = index
if (item.children && item.children.length > 0) {
threeItemList.value = item.children
}
}
const threeClick = (item) => {
currentItemThree.value = item.id
emit('positionChange', {
id: item.id, displayName: item.displayName })
emit('update:positionShow', false)
}
// 职位类型选择树结构
const parentId = ref('1')
const {
data: positionTypeData, run: getSearchData } = useRequest(getPositionCategoryTree, {
defaultParams: [{
}],
onSuccess (data) {
console.log(twoItemList.value)
twoItemList.value = data?.filter(item => item?.id === parentId.value)[0]?.children
if (positionId.value && data?.length > 0) {
console.log('nininniinni')
const array = positionTypeData.value.map(item => item.children?.map(e => e.children.filter(l => l.id === positionId.value)))
// 合并数组
const newArr = array?.reduce((pre, curr, index, array) => {
return pre?.concat(curr)
})
// 去除空数组
const result = newArr?.find(item => item && item.length !== 0)
// 二级id 回显
currentItemId.value = result[0].parentId
// 三级id回显
currentItemThree.value = positionId.value
const threeArray = positionTypeData.value.map(item => item?.children?.filter(e => e.id === currentItemId.value))
console.log(threeArray, 'threeArray')
// 一级id 回显
const oneArray = positionTypeData.value.map(item => item?.children?.filter(e => e.id === currentItemId.value))
if (oneArray && oneArray.length > 0) {
const oneResult = oneArray?.find(item => item && item.length !== 0)
if (oneResult && oneResult.length > 0) {
parentId.value = oneResult[0]?.parentId
twoItemList.value = positionTypeData.value.filter(item => item.id === parentId.value)[0]?.children
threeItemList.value = oneResult[0]?.children
if (threeItemList.value && threeItemList.value.length > 0) {
isShowThree.value = true
}
}
}
convertList.value.forEach((item, index) => {
item.forEach((i) => {
i.children.forEach(l => {
if (l.id === positionId.value) {
currentRowIndex.value = index
}
})
})
})
}
console.log(currentRowIndex.value, ' currentRowIndex.value')
}
})
// 搜索职位类型
const positionSearchData = ref([])
const {
run: getSearchHandler } = useRequest(positionSearch, {
manual: true,
onSuccess (data) {
positionSearchData.value = data
}
})
// 模糊搜索框
const positionName = ref(null)
const searchChange = (val) => {
if (val) {
const obj = positionSearchData.value.find(e => e.id === val)
emit('positionChange', {
id: obj.id, displayName: obj.displayName })
emit('update:positionShow', false)
}
}
const remoteMethod = (val) => {
if (val) {
getSearchHandler({
keyword: val })
}
}
const getSearch = () => {
console.log('搜索')
}
watch(() => positionId.value,
(val, oldVal) => {
if (val) {
getSearchData({
})
}
}, {
immediate: true, deep: true })
// 关闭弹窗
const closeBtn = () => {
positionName.value = null
currentRowIndex.value = -1
isShowThree.value = false
emit('update:positionShow', false)
}
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: auto;
border-top: 1px solid rgba(0, 0, 0, 0.04);
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
position: relative;
margin-top: 15px;
box-sizing: border-box;
display: flex;
.left {
width: 217px;
z-index: 10;
font-size: 16px;
font-weight: 500;
color: #333333;
max-height: 473px;
overflow: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
background-color: #FFFFFF;
width: 5px;
}
/* 滚动条的滑轨背景颜色 */
&::-webkit-scrollbar-thumb {
background-color: var(--el-color-primary);
border-radius: 7px;
width: 5px;
}
.item {
box-sizing: border-box;
background-color: rgba(36, 182, 167, 0.1);
margin-bottom: 1px;
width: 100%;
height: 54px;
line-height: 54px;
overflow: hidden;
padding-left: 30px;
padding-right: 20px;
cursor: pointer;
&.actived {
background-color: #fff;
position: relative;
color: var(--el-color-primary);
&::before {
content: ' ';
width: 4px;
height: 100%;
position: absolute;
background-color: var(--el-color-primary);
left: 0px;
top: 0px
}
}
}
}
.right {
padding: 24px 29px;
position: relative;
left: 6px;
top: 0px;
width: calc(100% - 217px);
height: 100%;
padding-left: 24px;
box-sizing: border-box;
overflow: auto;
:deep(.zlzp-checkbox) {
width: 100%;
.zlzp-checkbox__label {
width: 100%;
box-sizing: border-box;
}
}
.checkbox {
margin-bottom: 20px;
}
}
}
.select_text {
&.selected {
color: var(--el-color-primary);
}
}
.select_text:hover {
cursor: pointer;
color: var(--el-color-primary);
}
.flex {
display: flex;
align-items: center;
}
.two_tree {
position: relative;
}
.right_box {
display: flex;
align-items: center;
flex-wrap: wrap;
width: 632px;
.row {
display: flex;
padding-bottom: 20px;
}
.row_space {
margin-bottom: 225px;
}
.text_box {
width: 150px;
}
.relative_box {
position: relative;
color: var(--el-color-primary);
}
.text_icon {
width: 130px;
font-size: 14px;
font-weight: 500;
color: #666666;
text-overflow: ellipsis;
overflow: hidden;
cursor: pointer;
}
.position_icon {
padding-right: 11px;
}
.three_tree {
position: absolute;
top: 50px;
height: 211px;
width: 632px;
border: 1px solid #EDEDED;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
color: #999999;
box-sizing: border-box;
padding: 26px 25px 0;
overflow: hidden;
overflow-y: scroll;
&::-webkit-scrollbar {
background-color: #FFFFFF;
width: 5px;
}
/* 滚动条的滑轨背景颜色 */
&::-webkit-scrollbar-thumb {
background-color: #EBEBEB;
border-radius: 7px;
width: 5px;
}
.three_item {
font-size: 14px;
font-weight: 500;
color: #999999;
padding-bottom: 38px;
}
.three_selected {
color: var(--el-color-primary);
}
}
}
.header_box {
.header_title {
padding-right: 70px;
font-size: 28px;
font-weight: bold;
color: #333333;
}
:deep(.zlzp-select .zlzp-input) {
width: 281px;
height: 39px;
}
:deep(.zlzp-select-dropdown__item) {
height: 50px;
line-height: 50px;
padding: 0 10px !important;
}
:deep(.zlzp-popper) {
padding: 0 !important;
}
}
.dialog {
:deep(.zlzp-dialog__footer) {
text-align: center;
}
:deep(.zlzp-dialog__body) {
padding: 0px
}
:deep(.zlzp-dialog__headerbtn) {
top: 13px;
right: 20px;
}
}
</style>