跳到主要内容

自定义级联组件

CascaderTime

constants.js

/**
* 日期类型级联列表
*/
export const dateOptionsList = [
{
label: '按天',
value: 'day',
children: [
{
label: '当日',
parentType: 'day',
value: '0',
},
{
label: '次日',
parentType: 'day',
value: '1',
},
{
label: '7日',
parentType: 'day',
value: '7',
},
{
label: '14日',
parentType: 'day',
value: '14',
},
{
label: '30日',
parentType: 'day',
value: '30',
isCustom: true,
defalutValue: 30,
},
],
},
{
label: '按周',
value: 'week',
children: [
{
label: '当周',
parentType: 'week',
value: '0',
},
{
label: '次周',
parentType: 'week',
value: '1',
},
{
label: '4周',
parentType: 'week',
value: '4',
},
{
label: '8周',
parentType: 'week',
value: '8',
},
{
label: '16周',
value: '16',
parentType: 'week',
isCustom: true,
defalutValue: 16,
},
],
},
{
label: '按月',
value: 'month',
children: [
{
label: '当月',
parentType: 'month',
value: '0',
},
{
label: '次月',
parentType: 'month',
value: '1',
},
{
label: '3月',
parentType: 'month',
value: '3',
},
{
label: '6月',
parentType: 'month',
value: '6',
},
{
label: '12月',
value: '12',
parentType: 'month',
isCustom: true,
defalutValue: 12,
},
],
},
]

/**
* 日期类型映射
*/
export const dateMap = {
day: '日',
week: '周',
month: '月',
}

代码

<template>
<div>
<el-select
:disabled="disabled"
placeholder="请选择"
:value="timeSelect.name"
ref="treeSelect"
@visible-change="changeVisible"
popper-class="cascader-time-dropdown"
:popper-append-to-body="true"
style="width: 120px;"
:style="{ width: isFromDashboard ? '100px' : '120px', marginRight: isFromDashboard ? '5px' : '0' }"
>
<el-option v-show="false" value=""></el-option>
<div style="position: relative">
<div class="todos">
<div v-for="(group, index) in cascaderList" :key="group.label">
<div class="content">
<div class="mul" :class="timeSelect.parentType === group.value ? 'active' : ''" @mouseover="handleMouseOver(group)">
<div style="font-size: 14px">{{ group.label }}</div>
<div>
<i class="el-icon-right" style="font-size: 14px"></i>
</div>
</div>
</div>
</div>
<div class="tooltip" v-if="cascaderChildren">
<div class="card">
<div v-for="item in cascaderChildren || []" :key="item.label">
<div class="mul" :class="handleShowHoverClass(item)" @click.stop="changeTimeUnit(cascaderItems.value, item)">
<div v-if="!item.isCustom" class="content">{{ item.label }}</div>
<div v-else style="display: flex; margin-top: 5px">
<el-input-number
style="width: 60px"
v-model="timeCustomValue"
:min="1"
@focus="timecustomFocus"
@blur="timecustomBlur"
@change="timeCustomValueChange"
:controls="false"
></el-input-number>
<span style="margin-left: 5px">{{ dateMap[item.parentType] }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-select>
</div>
</template>
<script>
import { dateOptionsList, dateMap } from './constants'
export default {
name: 'CascaderTime',
props: {
disabled: {
type: Boolean
},
isFromDashboard: {
type: Boolean,
default: false,
},
timeSelect: {
type: Object,
},
defaultValue: {
type: Object,
},
},
data() {
return {
dateMap,
cascaderList: [],
cascaderItems: null,
cascaderChildren: null,
timeCustomValue: 0,
isTimeCustomFocus: false,
}
},
mounted() {
this.$emit('init', this.timeSelect)
this.changeVisible(true)
},
methods: {
/**
* 选中改变
* @param {*} type
* @param {*} item
*/
changeTimeUnit(type, item) {
if (this.isTimeCustomFocus) return
const { value, defalutValue, label, parentType, isCustom } = item
let result;
if (isCustom) {
result = { name: `${this.timeCustomValue}${dateMap[parentType]}`, ...item, customValue: this.timeCustomValue }
} else {
result = { name: label, ...item }
}
this.$refs.treeSelect.visible = false
this.$emit('change', result)
},
handleMouseOver(group) {
this.cascaderItems = group
this.cascaderChildren = group.children
this.timeCustomValue =
group.children.find((it) => it.isCustom).parentType === this.timeSelect.parentType && this.timeSelect.customValue
? this.timeSelect.customValue
: group.children.find((it) => it.isCustom)?.defalutValue
},
timecustomFocus(e) {
this.isTimeCustomFocus = true
},
timecustomBlur(e) {
this.isTimeCustomFocus = false
},
timeCustomValueChange(val) {
this.isTimeCustomFocus = true
this.timeCustomValue = val
},
changeVisible(show) {
this.timeSelect = !this.timeSelect.name ? this.defaultValue : this.timeSelect
if (show) {
this.cascaderList = dateOptionsList
// 回显数据展示
let groupItems = dateOptionsList.find((it) => it.value === this.timeSelect.parentType)
this.cascaderItems = groupItems
this.cascaderChildren = groupItems.children
this.timeCustomValue = this.timeSelect.customValue || groupItems.children.find((it) => it.isCustom)?.defalutValue || 30
} else {
this.cascaderChildren = null
this.cascaderItems = null
}
},
handleShowHoverClass(item) {
// 自定义
if (item.parentType === this.timeSelect.parentType && this.timeSelect.customValue) {
return this.timeSelect.defalutValue === item.defalutValue ? 'active' : ''
}
return this.timeSelect.name === item.label ? 'active' : ''
},
},
}
</script>
<style lang="scss" src="./index.scss"></style>

index.scss

.cascader-time-dropdown {
.tooltip {
position: absolute;
right: -3px;
transform: translateX(100%);
top: -8px;
min-width: 150px;
.card {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
border: 1px solid #ebeef5;
background: #fff;
padding: 0;
font-size: 12px;
}
}
.mul {
line-height: 40px;
padding-left: 20px;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 10px;
&:hover {
background: var(--98du-defalut-bgc);
}
&.active {
color: var(--98du-primary-color);
background: var(--98du-defalut-bgc);
}
}
.content {
color: #666;
font-size: 12px;
}
.el-scrollbar {
overflow: inherit !important;
}
.el-scrollbar__wrap {
margin: 0 !important;
overflow: inherit !important;
&::-webkit-scrollbar {
overflow: hidden;
}
}
.el-scrollbar__bar {
display: none;
}

.el-form-item {
margin-bottom: 0;
}
.el-form-item__error {
top: 50%;
left: auto;
right: 10px;
transform: translateY(-50%);
padding-top: 0;
user-select: none;
}
.el-form-item.is-error .el-date-editor.el-range-editor.el-input__inner {
border-color: #ff4949;
}
.el-date-editor.el-range-editor.el-input__inner:hover {
border-color: var(--98du-primary-color);
}
.el-form-item--medium .el-form-item__content {
line-height: 36px;
}
}