feat(图表): 下钻字段支持重命名和排序 #9700 #12491

This commit is contained in:
wisonic 2024-10-19 22:49:28 +08:00
parent 5196158c33
commit 2c20d8a76c
8 changed files with 362 additions and 27 deletions

View File

@ -312,7 +312,6 @@ public class ChartDataManage {
ChartViewFieldDTO nextDrillField = drill.get(i + 1);
if (!fields.contains(nextDrillField.getId())) {
nextDrillField.setSource(FieldSource.DRILL);
nextDrillField.setSort(getDrillSort(xAxis, drill.get(0)));
xAxis.add(nextDrillField);
dillAxis.add(nextDrillField);
fields.add(nextDrillField.getId());
@ -791,4 +790,24 @@ public class ChartDataManage {
}
}
}
public List<String> getDrillFieldData(ChartViewDTO view, Long fieldId) throws Exception {
List<ChartViewFieldDTO> drillField = view.getDrillFields();
ChartViewFieldDTO targetField = null;
for (int i = 0; i < drillField.size(); i++) {
ChartViewFieldDTO tmp = drillField.get(i);
if (tmp.getId().equals(fieldId)) {
targetField = tmp;
break;
}
}
if (targetField == null) {
return Collections.emptyList();
}
view.setXAxis(Collections.singletonList(targetField));
List<String[]> sqlData = sqlData(view, view.getChartExtRequest(), fieldId);
List<String[]> result = customSort(Optional.ofNullable(targetField.getCustomSort()).orElse(new ArrayList<>()), sqlData, 0);
return result.stream().map(i -> i[0]).distinct().collect(Collectors.toList());
}
}

View File

@ -303,4 +303,9 @@ public class ChartDataServer implements ChartDataApi {
public List<String> getFieldData(ChartViewDTO view, Long fieldId, String fieldType) throws Exception {
return chartDataManage.getFieldData(view, fieldId, fieldType);
}
@Override
public List<String> getDrillFieldData(ChartViewDTO view, Long fieldId) throws Exception {
return chartDataManage.getDrillFieldData(view, fieldId);
}
}

View File

@ -88,7 +88,7 @@ export const saveChart = async (data): Promise<IResponse> => {
}
// 获取单个字段枚举值
export const getFieldData = async (fieldId, fieldType, data): Promise<IResponse> => {
export const getFieldData = async ({ fieldId, fieldType, data }): Promise<IResponse> => {
delete data.data
return request
.post({ url: `/chartData/getFieldData/${fieldId}/${fieldType}`, data })
@ -97,6 +97,14 @@ export const getFieldData = async (fieldId, fieldType, data): Promise<IResponse>
})
}
// 获取下钻字段枚举值
export const getDrillFieldData = async ({ fieldId, data }): Promise<IResponse> => {
delete data.data
return request.post({ url: `/chartData/getDrillFieldData/${fieldId}`, data }).then(res => {
return res
})
}
export const getChartDetail = async (id: string): Promise<IResponse> => {
return request.post({ url: `chart/getDetail/${id}`, data: {} }).then(res => {
return res

View File

@ -58,6 +58,7 @@ declare type AxisType =
| 'flowMapStartName'
| 'flowMapEndName'
| 'extColor'
| 'drillFields'
/**
* 轴配置
*/

View File

@ -1,10 +1,15 @@
<script lang="tsx" setup>
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import icon_down_outlined1 from '@/assets/svg/icon_down_outlined-1.svg'
import icon_sortAToZ_outlined from '@/assets/svg/icon_sort-a-to-z_outlined.svg'
import icon_sortZToA_outlined from '@/assets/svg/icon_sort-z-to-a_outlined.svg'
import icon_sort_outlined from '@/assets/svg/icon_sort_outlined.svg'
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import { useI18n } from '@/hooks/web/useI18n'
import { onMounted, ref, toRefs, watch } from 'vue'
import { getItemType } from '@/views/chart/components/editor/drag-item/utils'
import { Delete } from '@element-plus/icons-vue'
import { fieldType } from '@/utils/attr'
import { iconFieldMap } from '@/components/icon-group/field-list'
@ -43,7 +48,12 @@ const props = defineProps({
}
})
const emit = defineEmits(['onDimensionItemRemove'])
const emit = defineEmits([
'onDimensionItemRemove',
'onCustomSort',
'onDimensionItemChange',
'onNameEdit'
])
const { item } = toRefs(props)
@ -60,6 +70,9 @@ const clickItem = param => {
return
}
switch (param.type) {
case 'rename':
showRename()
break
case 'remove':
removeItem()
break
@ -79,6 +92,35 @@ const removeItem = () => {
const getItemTagType = () => {
tagType.value = getItemType(props.dimensionData, props.quotaData, props.item)
}
const showRename = () => {
item.value.index = props.index
item.value.renameType = 'drillFields'
emit('onNameEdit', item.value)
}
const sort = param => {
if (param.type === 'custom_sort') {
const item = {
index: props.index,
sort: param.type
}
emit('onCustomSort', item)
} else {
item.value.index = props.index
item.value.sort = param.type
item.value.customSort = []
delete item.value.axisType
emit('onDimensionItemChange', item.value)
}
}
const beforeSort = type => {
return {
type: type
}
}
onMounted(() => {
getItemTagType()
})
@ -89,21 +131,42 @@ onMounted(() => {
<el-dropdown :effect="themes" trigger="click" size="mini" @command="clickItem">
<el-tag
class="item-axis father"
:class="'editor-' + props.themes"
:class="['editor-' + props.themes, `${themes}_icon-right`]"
:style="{ backgroundColor: tagType + '0a', border: '1px solid ' + tagType }"
>
<span style="display: flex">
<span style="display: flex; color: #646a73">
<el-icon v-if="'asc' === item.sort">
<Icon name="icon_sort-a-to-z_outlined"
><icon_sortAToZ_outlined class="svg-icon"
/></Icon>
</el-icon>
<el-icon v-if="'desc' === item.sort">
<Icon name="icon_sort-z-to-a_outlined"
><icon_sortZToA_outlined class="svg-icon"
/></Icon>
</el-icon>
<el-icon v-if="'custom_sort' === item.sort">
<Icon name="icon_sort_outlined"><icon_sort_outlined class="svg-icon" /></Icon>
</el-icon>
<el-icon>
<Icon :className="`field-icon-${fieldType[item.deType]}`"
<Icon :className="`field-icon-${fieldType[[2, 3].includes(item.deType) ? 2 : 0]}`"
><component
class="svg-icon"
:class="`field-icon-${fieldType[item.deType]}`"
:class="`field-icon-${fieldType[[2, 3].includes(item.deType) ? 2 : 0]}`"
:is="iconFieldMap[fieldType[item.deType]]"
></component
></Icon>
</el-icon>
</span>
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
<el-tooltip
:effect="themes === 'dark' ? 'ndark' : 'dark'"
placement="top"
:content="item.chartShowName ? item.chartShowName : item.name"
>
<span class="item-span-style">
<span class="item-name">{{ item.chartShowName ? item.chartShowName : item.name }}</span>
</span>
</el-tooltip>
<el-icon class="child remove-icon" size="14px">
<Icon name="icon_delete-trash_outlined" class-name="inner-class"
><icon_deleteTrash_outlined @click="removeItem" class="svg-icon inner-class"
@ -117,8 +180,105 @@ onMounted(() => {
</el-icon>
</el-tag>
<template #dropdown>
<el-dropdown-menu :effect="themes" class="drop-style">
<el-dropdown-item :icon="Delete" :command="beforeClickItem('remove')">
<el-dropdown-menu
:effect="themes"
class="drop-style"
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
>
<el-dropdown-item @click.prevent>
<el-dropdown
:effect="themes"
popper-class="data-dropdown_popper_mr9"
placement="right-start"
style="width: 100%; height: 100%"
@command="sort"
>
<span class="inner-dropdown-menu menu-item-padding">
<span class="menu-item-content">
<el-icon>
<Icon name="icon_sort_outlined"><icon_sort_outlined class="svg-icon" /></Icon>
</el-icon>
<span>{{ t('chart.sort') }}</span>
<span class="summary-span-item">({{ t('chart.' + props.item.sort) }})</span>
</span>
<el-icon>
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu
:effect="themes"
class="drop-style sub"
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
>
<el-dropdown-item class="menu-item-padding" :command="beforeSort('none')">
<span
class="sub-menu-content"
:class="'none' === item.sort ? 'content-active' : ''"
>
{{ t('chart.none') }}
<el-icon class="sub-menu-content--icon">
<Icon name="icon_done_outlined" v-if="'none' === item.sort"
><icon_done_outlined class="svg-icon"
/></Icon>
</el-icon>
</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeSort('asc')">
<span
class="sub-menu-content"
:class="'asc' === item.sort ? 'content-active' : ''"
>
{{ t('chart.asc') }}
<el-icon class="sub-menu-content--icon">
<Icon name="icon_done_outlined" v-if="'asc' === item.sort"
><icon_done_outlined class="svg-icon"
/></Icon>
</el-icon>
</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeSort('desc')">
<span
class="sub-menu-content"
:class="'desc' === item.sort ? 'content-active' : ''"
>
{{ t('chart.desc') }}
<el-icon class="sub-menu-content--icon">
<Icon name="icon_done_outlined" v-if="'desc' === item.sort"
><icon_done_outlined class="svg-icon"
/></Icon>
</el-icon>
</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeSort('custom_sort')">
<span
class="sub-menu-content"
:class="'custom_sort' === item.sort ? 'content-active' : ''"
>
{{ t('chart.custom_sort') }}{{ t('chart.sort') }}
<el-icon class="sub-menu-content--icon">
<Icon name="icon_done_outlined" v-if="'custom_sort' === item.sort"
><icon_done_outlined class="svg-icon"
/></Icon>
</el-icon>
</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeClickItem('rename')">
<el-icon>
<icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></icon>
</el-icon>
<span>{{ t('chart.show_name_set') }}</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" divided :command="beforeClickItem('remove')">
<el-icon>
<icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></icon>
</el-icon>
<span>{{ t('chart.delete') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
@ -128,6 +288,16 @@ onMounted(() => {
</template>
<style lang="less" scoped>
:deep(.ed-dropdown-menu__item) {
padding: 0;
}
:deep(.ed-dropdown-menu__item.menu-item-padding) {
padding: 5px 16px;
}
.menu-item-padding {
padding: 5px 16px;
}
.item-style {
position: relative;
width: 100%;
@ -145,7 +315,6 @@ onMounted(() => {
.item-axis {
padding: 1px 8px;
margin: 0 3px 2px 3px;
text-align: left;
height: 28px;
line-height: 28px;
display: flex;
@ -169,7 +338,9 @@ span {
.summary-span {
margin-left: 4px;
color: #a6a6a6;
color: #878d9f;
position: absolute;
right: 25px;
}
.inner-dropdown-menu {
@ -177,6 +348,27 @@ span {
justify-content: space-between;
align-items: center;
width: 100%;
.menu-item-content {
display: flex;
flex-direction: row;
align-items: center;
}
}
.sub-menu-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
&.content-active {
color: var(--ed-color-primary);
}
.sub-menu-content--icon {
margin-left: 8px;
}
}
.item-span-drop {
@ -185,13 +377,17 @@ span {
}
.item-span-style {
display: inline-block;
width: 115px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
max-width: 180px;
color: #1f2329;
margin-left: 4px;
.item-name {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.editor-dark {
@ -200,29 +396,113 @@ span {
}
}
.summary-span-item {
margin-left: 4px;
}
.drop-style {
:deep(.ed-dropdown-menu__item) {
height: 32px;
min-width: 218px;
}
&.sub {
:deep(.ed-dropdown-menu__item) {
min-width: 118px;
}
}
:deep(.ed-dropdown-menu__item:not(.is_disabled):focus) {
color: inherit;
background-color: rgba(31, 35, 41, 0.1);
}
&.dark-dimension-quota {
.inner-dropdown-menu {
color: rgba(235, 235, 235, 1);
}
:deep(.ed-dropdown-menu__item) {
color: rgba(235, 235, 235, 1);
}
:deep(.ed-dropdown-menu__item.is-disabled) {
color: #a6a6a6;
}
:deep(.ed-dropdown-menu__item:not(.is_disabled):focus) {
background-color: rgba(235, 235, 235, 0.1);
}
}
}
.remove-icon {
position: absolute;
top: 7px;
right: 24px;
color: #646a73;
right: 26px;
cursor: pointer;
.inner-class {
font-size: 14px;
}
}
.father .child {
visibility: hidden;
.father {
&.dark_icon-right {
.child {
color: #a6a6a6;
}
}
&.light_icon-right {
.child {
color: #646a73;
}
}
.child {
font-size: 14px;
visibility: hidden;
}
}
.father:hover .child {
visibility: visible;
}
.father:hover .item-span-style {
max-width: 150px;
}
</style>
<style lang="less">
.data-dropdown_popper_mr9 {
margin-left: -9px !important;
}
.menu-item-padding {
span {
font-size: 14px;
color: #1f2329;
}
.ed-icon {
color: #646a73;
font-size: 16px !important;
}
.sub-menu-content--icon {
color: var(--ed-color-primary);
margin-right: -7px;
}
:nth-child(1).ed-icon {
margin-right: 8px;
}
.menu-item-content {
:nth-child(1).ed-icon {
margin-right: 8px;
}
}
}
.dark-dimension-quota {
span {
color: #ebebeb;
}
.ed-icon {
color: #a6a6a6;
}
.sub-menu-content--icon {
color: var(--ed-color-primary);
margin-right: -7px !important;
}
}
</style>

View File

@ -1,7 +1,7 @@
<script lang="tsx" setup>
<script lang="ts" setup>
import icon_drag_outlined from '@/assets/svg/icon_drag_outlined.svg'
import draggable from 'vuedraggable'
import { getFieldData } from '@/api/chart'
import { getFieldData, getDrillFieldData } from '@/api/chart'
import { reactive, watch, ref } from 'vue'
import { useCache } from '@/hooks/web/useCache'
@ -45,7 +45,13 @@ const init = () => {
user: wsCache.get('user.uid')
}
}
getFieldData(props.field.id, props.fieldType, chart)
const param = {
fieldId: props.field.id,
fieldType: props.fieldType,
data: chart
}
let reqMethod = props.fieldType === 'drillFields' ? getDrillFieldData : getFieldData
reqMethod(param)
.then(response => {
const strArr = response.data
state.sortList = strArr.map(ele => {

View File

@ -531,6 +531,12 @@ const onCustomExtColorSort = item => {
customSortAxis.value = 'extColor'
customSort()
}
const onDrillCustomSort = item => {
recordSnapshotInfo('render')
state.customSortField = view.value.drillFields[item.index]
customSortAxis.value = 'drillFields'
customSort()
}
const onMove = e => {
recordSnapshotInfo('calcData')
state.moveId = e.draggedContext.element.id
@ -1256,6 +1262,10 @@ const saveRename = ref => {
axis = view.value.extColor[index]
view.value.extColor[index].chartShowName = chartShowName
break
case 'drillFields':
axisType = 'drillFields'
axis = view.value.drillFields[index]
view.value.drillFields[index].chartShowName = chartShowName
default:
break
}
@ -2826,6 +2836,8 @@ const deleteChartFieldItem = id => {
:themes="props.themes"
@onDimensionItemChange="drillItemChange"
@onDimensionItemRemove="drillItemRemove"
@onNameEdit="showRename"
@onCustomSort="onDrillCustomSort"
/>
</template>
</draggable>

View File

@ -29,4 +29,8 @@ public interface ChartDataApi {
@Operation(summary = "获取字段值")
@PostMapping("getFieldData/{fieldId}/{fieldType}")
List<String> getFieldData(@RequestBody ChartViewDTO view, @PathVariable Long fieldId, @PathVariable String fieldType) throws Exception;
@Operation(summary = "获取下钻字段值")
@PostMapping("getDrillFieldData/{fieldId}")
List<String> getDrillFieldData(@RequestBody ChartViewDTO view, @PathVariable Long fieldId) throws Exception;
}