<script lang="ts" setup>
import icon_info_colorful from '@/assets/svg/icon_info_colorful.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import joinJoin from '@/assets/svg/join-join.svg'
import { ref, shallowRef } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ElMessage } from 'element-plus-secondary'
import { guid } from '@/views/visualized/data/dataset/form/util.js'
import { listByDsIds } from '@/api/dataset'
import { cloneDeep } from 'lodash-es'
interface Cascade {
datasetId: string
name: string
queryId: string
deType: string
fieldId: string
isTree: boolean
}
type cascadeMap = Record<string, Cascade>
const { t } = useI18n()
let deTypeMap = shallowRef({})
const emits = defineEmits(['saveCascade'])
const dialogVisible = ref(false)
const handleBeforeClose = () => {
dialogVisible.value = false
}
const cascadeList = ref([])
const optionsMap = shallowRef({})
const datasetMap = shallowRef([])
const cancelClick = () => {
handleBeforeClose()
}
const confirmClick = () => {
const { isError, arr } = setCascadeArrBack()
if (isError) {
ElMessage.error(t('v_query.cannot_be_empty'))
return
}
emits('saveCascade', arr)
handleBeforeClose()
}
const setCascadeArrBack = () => {
let isError = false
const arr = cloneDeep(cascadeList.value)
arr.forEach(ele => {
ele.forEach(item => {
if (!item.placeholder && !item.fieldId) {
isError = true
}
if (!item.datasetId) {
isError = true
}
item.selectValue = []
})
})
return {
arr,
isError
}
}
const init = (cascadeMap: cascadeMap, arr) => {
cascadeList.value = cloneDeep(arr)
datasetMap.value = Object.values(cascadeMap).map(ele => ({
label: ele.name,
isTree: ele.isTree,
deType: ele.deType,
value: `${ele.datasetId}--${ele.queryId}--${ele.fieldId}`
}))
let obj = {}
Object.values(cascadeMap).forEach(ele => {
obj[`${ele.datasetId}--${ele.queryId}--${ele.fieldId}`] = ele.deType
})
deTypeMap.value = obj
listByDsIds(datasetMap.value.map(ele => ele.value.split('--')[0]))
.then(res => {
optionsMap.value = res
})
.finally(() => {
dialogVisible.value = true
})
}
const disabledDatasetId = shallowRef([])
const visibleChange = (val, index, idx) => {
let topId = ''
let topIdArr = []
let bottomId = ''
let bottomIdArr = []
for (let i in cascadeList.value[index]) {
if (i > idx) {
if (cascadeList.value[index][i].datasetId && !bottomId) {
bottomId = cascadeList.value[index][i].datasetId
}
continue
}
if (cascadeList.value[index][i].datasetId) {
topId = cascadeList.value[index][i].datasetId
}
if (i === idx) {
topId = (cascadeList.value[index][idx - 1] || {}).datasetId
}
}
cascadeList.value.forEach(ele => {
let tentativeTopArr = []
let tentativeBottomArr = []
for (let i in ele) {
if (topIdArr[topIdArr.length - 1] === tentativeTopArr || bottomId === ele[i].datasetId) {
if (bottomId === ele[i].datasetId) {
bottomIdArr.push(tentativeBottomArr)
}
if (bottomIdArr[bottomIdArr.length - 1] === tentativeBottomArr) {
tentativeBottomArr.push(ele[i].datasetId)
}
continue
}
if (ele[i].datasetId) {
tentativeTopArr.push(ele[i].datasetId)
}
if (topId === ele[i].datasetId) {
topIdArr.push(tentativeTopArr)
}
}
})
if (val) {
disabledDatasetId.value = [...new Set([...topIdArr.flat(), ...bottomIdArr.flat()])].filter(
ele => !!ele
)
}
}
const addCascadeItem = item => {
item.push({
datasetId: '',
fieldId: '',
placeholder: item.length ? '' : t('v_query.the_first_level'),
id: guid()
})
}
const setPlaceholder = () => {
cascadeList.value.forEach(ele => {
ele.forEach((item, idx) => {
if (idx) {
item.placeholder = ''
}
if (
item &&
ele[idx - 1] &&
item.datasetId &&
item.datasetId.split('--')[0] === ele[idx - 1].datasetId.split('--')[0]
) {
item.placeholder = t('v_query.configure_cascaded_fields')
item.fieldId = ''
}
})
})
}
const deleteCascade = (idx, item) => {
item.splice(idx, 1)
item[0].fieldId = ''
item[0].placeholder = t('v_query.the_first_level')
setPlaceholder()
}
const deleteCascadeBlock = idx => {
cascadeList.value.splice(idx, 1)
}
const addCascadeBlock = () => {
const arr = []
addCascadeItem(arr)
cascadeList.value.push(arr)
}
const indexNumCascade = [
t('visualization.number1'),
t('visualization.number2'),
t('visualization.number3'),
t('visualization.number4'),
t('visualization.number5')
]
defineExpose({
init
})
</script>
<template>
<el-dialog
class="query-condition-cascade"
v-model="dialogVisible"
width="900px"
@click.stop
:before-close="handleBeforeClose"
@mousedown.stop
@mousedup.stop
>
<template #title>
<div class="title">
{{ t('v_query.condition_cascade_configuration')
}}<span style="margin-left: 8px" class="tip">{{ t('v_query.not_reverse_cascade') }}</span>
</div>
</template>
<div class="content">
<el-icon style="font-size: 16px">
<Icon name="icon_info_colorful"><icon_info_colorful class="svg-icon" /></Icon>
</el-icon>
{{ t('v_query.must_be_met') }}<br />
{{ t('v_query.select_data_set') }}<br />
</div>
<el-button text @click="addCascadeBlock">
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('v_query.add_cascade_configuration') }}
</el-button>
<div class="cascade-content" v-for="(item, index) in cascadeList" :key="index">
<div style="display: flex; align-items: center; justify-content: space-between">
<el-button :disabled="item.length === 2" text @click="addCascadeItem(item)">
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('v_query.add_cascade_condition') }}
</el-button>
<el-button @click="deleteCascadeBlock(index)" class="cascade-delete-block" text>
<template #icon>
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</template>
</el-button>
</div>
<div class="cascade-item">
<div class="label">{{ t('v_query.query_condition_level') }}</div>
<div class="item-name">{{ t('v_query.select_query_condition') }}</div>
<div class="cascade-icon"></div>
<div class="item-field">{{ t('v_query.select_cascaded_field') }}</div>
</div>
<div class="cascade-item" v-for="(ele, idx) in item" :key="ele.id">
<div class="label">{{ t('v_query.level_1', { msg: indexNumCascade[idx] }) }}</div>
<div class="item-name">
<el-select
@visible-change="val => visibleChange(val, index, idx)"
v-model="ele.datasetId"
@change="setPlaceholder"
style="width: 300px"
>
<el-option
v-for="itx in datasetMap.filter(ele => (idx === 0 && !ele.isTree) || idx === 1)"
:key="itx.value"
:label="itx.label"
:value="itx.value"
:disabled="
(disabledDatasetId.includes(itx.value) &&
item.map(ele => ele.datasetId).includes(itx.value)) ||
(!!ele.datasetId && deTypeMap[ele.datasetId] !== itx.deType)
"
/>
</el-select>
</div>
<div class="cascade-icon">
<el-icon>
<Icon name="join-join"><joinJoin class="svg-icon" /></Icon>
</el-icon>
</div>
<div class="item-field">
<el-select
:placeholder="ele.placeholder"
:disabled="!!ele.placeholder"
v-model="ele.fieldId"
style="width: 300px"
>
<el-option
v-for="item in optionsMap[ele.datasetId.split('--')[0]]"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
<el-button v-show="idx !== 0" @click="deleteCascade(idx, item)" class="cascade-delete" text>
<template #icon>
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</template>
</el-button>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">{{ t('chart.cancel') }} </el-button>
<el-button @click="confirmClick" type="primary">{{ t('chart.confirm') }} </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="less" scoped>
.query-condition-cascade {
.title {
.tip {
font-size: 12px;
color: #646a73;
}
}
.content {
height: 62px;
width: 852px;
border-radius: 6px;
background: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.1));
position: relative;
padding: 9px 0 9px 40px;
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-weight: 400;
line-height: 20px;
.ed-icon {
position: absolute;
top: 10.6px;
left: 16px;
font-size: 14px;
color: var(--ed-color-primary, #3370ff);
}
margin-bottom: 16px;
}
.cascade-content {
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
border: 1px solid #e4e7ed;
padding: 24px;
padding-top: 8px;
margin-top: 8px;
.cascade-item {
display: flex;
align-items: center;
width: 100%;
height: 40px;
.label {
width: 100px;
}
.item-name {
width: 300px;
}
.cascade-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
font-size: 20px;
}
.item-field {
width: 300px;
}
}
.cascade-delete-block,
.cascade-delete {
width: 40px;
font-size: 20px;
color: #646a73;
margin-left: 20px;
}
}
}
</style>