Merge pull request #9049 from dataease/pr@dev-v2@feat_map_name_mapping

feat(图表): 地图支持地名映射 close #7514
This commit is contained in:
wisonic-s 2024-04-10 19:33:30 +08:00 committed by GitHub
commit 44c5733afe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 303 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import FunctionCfg from '@/views/chart/components/editor/editor-senior/component
import ScrollCfg from '@/views/chart/components/editor/editor-senior/components/ScrollCfg.vue' import ScrollCfg from '@/views/chart/components/editor/editor-senior/components/ScrollCfg.vue'
import AssistLine from '@/views/chart/components/editor/editor-senior/components/AssistLine.vue' import AssistLine from '@/views/chart/components/editor/editor-senior/components/AssistLine.vue'
import Threshold from '@/views/chart/components/editor/editor-senior/components/Threshold.vue' import Threshold from '@/views/chart/components/editor/editor-senior/components/Threshold.vue'
import MapMapping from '@/views/chart/components/editor/editor-senior/components/MapMapping.vue'
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue' import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import { computed, PropType, ref, toRefs, watch } from 'vue' import { computed, PropType, ref, toRefs, watch } from 'vue'
@ -35,7 +36,8 @@ const emit = defineEmits([
'onFunctionCfgChange', 'onFunctionCfgChange',
'onAssistLineChange', 'onAssistLineChange',
'onScrollCfgChange', 'onScrollCfgChange',
'onThresholdChange' 'onThresholdChange',
'onMapMappingChange'
]) ])
const props = defineProps({ const props = defineProps({
@ -113,6 +115,10 @@ const onThresholdChange = val => {
emit('onThresholdChange', val) emit('onThresholdChange', val)
} }
const onMapMappingChange = val => {
emit('onMapMappingChange', val)
}
const showProperties = (prop: EditorProperty) => { const showProperties = (prop: EditorProperty) => {
return properties?.value?.includes(prop) return properties?.value?.includes(prop)
} }
@ -196,6 +202,21 @@ const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
/> />
</el-collapse-item> </el-collapse-item>
<el-collapse-item
:effect="themes"
v-if="showProperties('map-mapping')"
name="mapMapping"
:title="t('chart.place_name_mapping')"
@modelChange="onFunctionCfgChange"
>
<map-mapping
:themes="themes"
:chart="props.chart"
:property-inner="propertyInnerAll['function-cfg']"
@onMapMappingChange="onMapMappingChange"
/>
</el-collapse-item>
<collapse-switch-item <collapse-switch-item
:effect="themes" :effect="themes"
:title="t('chart.assist_line')" :title="t('chart.assist_line')"

View File

@ -0,0 +1,271 @@
<script lang="ts" setup>
import { computed, nextTick, onMounted, PropType, reactive, ref, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { getGeoJsonFile, parseJson } from '../../../js/util'
import { forEach, debounce } from 'lodash-es'
import { EmptyBackground } from '@/components/empty-background'
const props = defineProps({
chart: {
type: Object as PropType<ChartObj>,
required: true
},
themes: {
type: String,
default: 'dark'
}
})
const emit = defineEmits(['onMapMappingChange'])
const { chart, themes } = props
watch(
[() => props.chart.senior.areaMapping, () => chart.customAttr.map.id],
() => {
init()
},
{ deep: true }
)
const editAreaId = ref('body')
const curMappedName = ref('')
const curOriginName = ref('')
const isEdit = ref(false)
const search = ref('')
const areaNameInput = ref(null)
const areaData = reactive([])
const dynamicAreaId = computed(() => {
return chart.customAttr.map.id
})
const state = reactive({
mappingForm: {},
currentData: []
})
const pageInfo = reactive({
pageSize: 10,
total: 100,
currentPage: 1
})
const init = async () => {
const chartObj = JSON.parse(JSON.stringify(chart))
if (chartObj.senior) {
let senior = parseJson(chartObj.senior)
state.mappingForm = senior.areaMapping
let curAreaMapping = state.mappingForm?.[dynamicAreaId.value]
if (!curAreaMapping) {
curAreaMapping = await getAreaMapping(dynamicAreaId.value)
}
const tmp = []
forEach(curAreaMapping, (val, key) => {
tmp.push({
originName: key,
mappedName: val
})
})
state.currentData.splice(0, state.currentData.length, ...tmp)
pageInfo.total = state.currentData.length
updateAreaData()
}
}
const getAreaMapping = async areaId => {
if (!areaId) {
return {}
}
const geoJson = await getGeoJsonFile(areaId)
return geoJson.features.reduce((p, n) => {
p[n.properties.name] = n.properties.name
return p
}, {})
}
const triggerEdit = scope => {
editAreaId.value = `#area-${scope.$index}-input`
curOriginName.value = scope.row.originName
curMappedName.value = scope.row.mappedName
isEdit.value = true
nextTick(areaNameInput.value?.focus)
}
const finishEdit = () => {
editAreaId.value = 'body'
isEdit.value = false
let areaNameMap = state.mappingForm[dynamicAreaId.value]
if (!areaNameMap) {
areaNameMap = state.currentData.reduce((p, n) => {
p[n.originName] = n.mappedName
return p
}, {})
}
const oldMappedName = areaNameMap[curOriginName.value]
if (oldMappedName === curMappedName.value) {
return
}
areaNameMap[curOriginName.value] = curMappedName.value
if (!state.mappingForm[dynamicAreaId.value]) {
state.mappingForm[dynamicAreaId.value] = areaNameMap
}
onMapMappingChange()
}
const updateAreaData = debounce(() => {
const filteredData = state.currentData.filter(item => {
if (!search.value?.trim()) {
return true
}
return item.mappedName?.includes(search.value)
})
const start = (pageInfo.currentPage - 1) * pageInfo.pageSize
const end = start + pageInfo.pageSize
areaData.splice(0, areaData.length, ...filteredData.slice(start, end))
pageInfo.total = filteredData.length
}, 300)
const onMapMappingChange = () => {
emit('onMapMappingChange', state.mappingForm)
}
const mergeCellMethod = ({ columnIndex }) => {
if (columnIndex === 1) {
return [1, 2]
}
if (columnIndex === 2) {
return [0, 0]
}
}
onMounted(() => {
init()
})
</script>
<template>
<div style="width: 100%">
<el-table
size="mini"
class="area-map-table"
:class="'area-map-table-' + themes"
:header-cell-class-name="'area-map-table-header-cell-' + themes"
:header-row-class-name="'area-map-table-header-row-' + themes"
:cell-class-name="'area-map-table-cell-' + themes"
:row-class-name="'area-map-table-row-' + themes"
:data="areaData"
:span-method="mergeCellMethod"
>
<el-table-column label="图形" prop="originName" width="80" show-overflow-tooltip />
<el-table-column width="144">
<template #header>
<span>属性</span>
<el-input
v-model="search"
size="small"
class="area-filter"
:effect="themes"
@input="updateAreaData"
/>
</template>
<template #default="scope">
<div :id="`area-${scope.$index}-input`"></div>
<el-button
v-show="!isEdit || editAreaId !== `#area-${scope.$index}-input`"
class="area-edit-btn"
size="small"
:class="'area-edit-btn-' + themes"
@click="triggerEdit(scope)"
>
<span>{{ scope.row.mappedName }}</span>
<el-icon><Edit /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty> <empty-background description="暂无数据" img-type="none" /> </template>
</el-table>
<el-pagination
small
hide-on-single-page
layout="prev, pager, next"
class="area-page"
v-model:current-page="pageInfo.currentPage"
:total="pageInfo.total"
:page-size="pageInfo.pageSize"
:pager-count="5"
@currentChange="updateAreaData"
/>
<teleport :to="editAreaId" :disabled="!isEdit">
<div v-show="isEdit">
<el-input
v-model="curMappedName"
size="small"
ref="areaNameInput"
:effect="themes"
@blur="finishEdit()"
@keyup.enter="$event.target.blur()"
/>
</div>
</teleport>
</div>
</template>
<style lang="less" scoped>
.area-filter {
display: inline-block;
width: 80px;
margin-left: 8px;
}
.area-edit-btn {
width: 120px;
padding: 0 4px;
:deep(> span) {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
span {
max-width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&-dark {
border-color: rgba(255, 255, 255, 0.3);
color: #fff;
background-color: transparent;
}
}
:deep(.area-page) {
button,
button[disabled] {
color: grey;
background: transparent !important;
&:active,
&:hover {
background: transparent !important;
}
}
ul li {
&:not(.is-active) {
color: grey;
}
background: transparent !important;
}
}
.area-map-table {
width: 100%;
font-size: 12px;
:deep(.area-map-table-header-cell-light) {
background-color: #f5f6f7;
}
:deep(.area-map-table-header-cell-dark) {
background-color: #1a1a1a;
color: @canvas-main-font-color-dark;
}
:deep(.area-map-table-row-dark) {
.area-map-table-cell-dark {
background-color: @side-content-background;
color: @canvas-main-font-color-dark;
border-bottom: 1px solid #373737;
}
&:hover {
.area-map-table-cell-dark {
background-color: #434343;
}
}
}
&-dark {
:deep(.ed-table__empty-block) {
background-color: @side-content-background;
}
}
}
</style>

View File

@ -787,6 +787,11 @@ const onThresholdChange = val => {
renderChart(view.value) renderChart(view.value)
} }
const onMapMappingChange = val => {
view.value.senior.areaMapping = val
renderChart(view.value)
}
const onScrollCfgChange = val => { const onScrollCfgChange = val => {
view.value.senior.scrollCfg = val view.value.senior.scrollCfg = val
renderChart(view.value) renderChart(view.value)
@ -1828,6 +1833,7 @@ const onRefreshChange = val => {
@onAssistLineChange="onAssistLineChange" @onAssistLineChange="onAssistLineChange"
@onScrollCfgChange="onScrollCfgChange" @onScrollCfgChange="onScrollCfgChange"
@onThresholdChange="onThresholdChange" @onThresholdChange="onThresholdChange"
@onMapMappingChange="onMapMappingChange"
/> />
</el-scrollbar> </el-scrollbar>
</el-container> </el-container>

View File

@ -1410,7 +1410,8 @@ export const BASE_VIEW_CONFIG = {
functionCfg: DEFAULT_FUNCTION_CFG, functionCfg: DEFAULT_FUNCTION_CFG,
assistLineCfg: DEFAULT_ASSIST_LINE_CFG, assistLineCfg: DEFAULT_ASSIST_LINE_CFG,
threshold: DEFAULT_THRESHOLD, threshold: DEFAULT_THRESHOLD,
scrollCfg: DEFAULT_SCROLL scrollCfg: DEFAULT_SCROLL,
areaMapping: {}
} }
} }

View File

@ -35,7 +35,8 @@ export const MAP_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'showQuota' 'showQuota'
], ],
'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'tooltipFormatter'], 'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'tooltipFormatter'],
'function-cfg': ['emptyDataStrategy'] 'function-cfg': ['emptyDataStrategy'],
'map-mapping': ['']
} }
export const MAP_AXIS_TYPE: AxisType[] = [ export const MAP_AXIS_TYPE: AxisType[] = [