| | |
| | | <template> |
| | | <div class="layer-control bg-white"> |
| | | <div class="w-[370px] bg-white p-3 rounded"> |
| | | <div class="layer-control bg-white flex-col" style="display: flex"> |
| | | <div class="bg-white rounded flex-0"> |
| | | <div class="header flex-items-center pb-1.5" style="border-bottom: 1px solid black"> |
| | | <div class="flex-items-center"> |
| | | <span class="ywifont ywicon-guanbi cursor-pointer mr-1.5" @click="closeClick"></span> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="content rounded-lg max-h-[700px] overflow-y-auto min-h-[600px]"> |
| | | <el-tree |
| | | <div class="flex-auto content rounded-lg overflow-y-auto"> |
| | | <!-- <el-tree |
| | | class="w-full" |
| | | :data="themeInfo" |
| | | :props="defaultProps" |
| | |
| | | <template #default="{ node, data }"> |
| | | <span>{{ node.label }}</span> |
| | | </template> |
| | | </el-tree> |
| | | </el-tree> --> |
| | | <div> |
| | | <div class="theme-item" v-for="themeGroup in themeInfo" :key="themeGroup.id"> |
| | | <div class="theme-item-title h-fit flex-items-center gap-2 mb-1"> |
| | | <div class="w-1 h-4 bg-[#1677ff]"></div> |
| | | <span class="font-bold">{{ themeGroup.title }}</span> |
| | | </div> |
| | | <div> |
| | | <el-radio-group v-model="themeGroup.activeTheme" class="w-full" @change="(val) => handleThemeChange(val, themeGroup)"> |
| | | <div class="flex w-full" v-for="(children, index) in themeGroup.viewChildren" :key="index"> |
| | | <el-radio v-for="item in children" :key="item.id" :label="item.id" class="flex-1">{{ item.title }}</el-radio> |
| | | </div> |
| | | </el-radio-group> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- <el-checkbox-group v-model="checkList" :max="1"> |
| | | <el-checkbox label="Option A" value="Value A" /> |
| | | <el-checkbox label="Option B" value="Value B" /> |
| | | <el-checkbox label="Option C" value="Value C" /> |
| | | <el-checkbox label="disabled" value="Value disabled" disabled /> |
| | | <el-checkbox label="selected and disabled" value="Value selected and disabled" disabled /> |
| | | </el-checkbox-group> --> |
| | | <div class="theme-item" v-for="legend in legendList" :key="legend.title"> |
| | | <div class="theme-item-title h-fit flex-items-center gap-2 mb-2"> |
| | | <div class="w-1 h-4 bg-[#1677ff]"></div> |
| | | <span class="font-bold">{{ legend.title }}</span> |
| | | </div> |
| | | <div class="theme-item-content"> |
| | | <el-row :gutter="0"> |
| | | <el-col v-for="item in legend.legend" :key="item.style" :span="12" class="mb20"> |
| | | <div class="flex-items-center gap-2"> |
| | | <div class="w-4 h-2 bg-[#1677ff]" :style="{ backgroundColor: `#${item.style}` }"></div> |
| | | <span>{{ item.label }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts" name="ThemeControl"> |
| | | import { computed, ref, watch, watchEffect } from 'vue'; |
| | | import type { ElTree } from 'element-plus'; |
| | | import { chunk } from 'lodash-es'; |
| | | import Circle from 'ol/style/Circle'; |
| | | import Style from 'ol/style/Style'; |
| | | import { computed, ref, watch } from 'vue'; |
| | | import { switchMapTheme } from '/@/api/map'; |
| | | import type { OLMap } from '/@/model/map/OLMap'; |
| | | import { ElTree } from 'element-plus'; |
| | | import { formatDate } from '/@/utils/formatTime'; |
| | | import { travelTree } from '/@/utils/util'; |
| | | import Fill from 'ol/style/Fill'; |
| | | import Stroke from 'ol/style/Stroke'; |
| | | import { Text } from 'ol/style'; |
| | | import { ElLoadingService } from 'element-plus'; |
| | | |
| | | const props = defineProps(['olMap']); |
| | | const emit = defineEmits(['close']); |
| | |
| | | emit('close'); |
| | | }; |
| | | const checkTreeRef = ref<InstanceType<typeof ElTree>>(); |
| | | const parseLegends = (legends) => { |
| | | const result = legends.map((item) => { |
| | | const isEqual = item.operate === '='; |
| | | const result = { |
| | | ...item, |
| | | legend: item.legend.map((legendItem, index, array) => { |
| | | const preItem = array[index - 1]; |
| | | let label = ''; |
| | | if (isEqual) { |
| | | label = legendItem.value; |
| | | } else if (preItem) { |
| | | label = `>${preItem.value}且${item.operate}${legendItem.value}`; |
| | | } else { |
| | | label = `${item.operate}${legendItem.value}`; |
| | | } |
| | | return { |
| | | ...legendItem, |
| | | label: label, |
| | | }; |
| | | }), |
| | | }; |
| | | |
| | | result.legend.push({ |
| | | style: item.default, |
| | | value: '', |
| | | label: result.legend.length == 0 ? '默认' : `>${item.legend[item.legend.length - 1].value}`, |
| | | }); |
| | | return result; |
| | | }); |
| | | return result; |
| | | }; |
| | | let isHumanCheckTrigger = false; |
| | | const handleCheck = (data: any, node: any) => { |
| | | const checkedKeys = node.checkedKeys; |
| | | isHumanCheckTrigger = true; |
| | | travelTree(themeInfo.value, (theme) => { |
| | | console.log("🚀 ~ theme.type:", theme.type) |
| | | |
| | | // 取消分组中旧的选项 |
| | | const excludeKeys = []; |
| | | for (const themeGroup of themeInfo.value) { |
| | | // 原来有多少个设置的 |
| | | const originSet = themeGroup.children.filter((item) => item.isSet); |
| | | // 现在有多少个设置的 |
| | | const newSet = themeGroup.children.filter((item) => checkedKeys.includes(item.id)); |
| | | if (originSet.length === 1 && newSet.length === 2) { |
| | | originSet[0].isSet = false; |
| | | excludeKeys.push(originSet[0].id); |
| | | } |
| | | } |
| | | |
| | | const realCheckedKeys = checkedKeys.filter((key) => !excludeKeys.includes(key)); |
| | | |
| | | travelTree(themeInfo.value, (theme, index, array, parent) => { |
| | | if (theme.type === 'theme') { |
| | | const id = theme.id; |
| | | if (!checkedKeys.includes(id)) { |
| | | if (!realCheckedKeys.includes(id)) { |
| | | theme.isSet = false; |
| | | } else { |
| | | theme.isSet = true; |
| | | } |
| | | } |
| | | }); |
| | | console.log('🚀 ~ human keys:', checkedKeys); |
| | | checkTreeRef.value?.setCheckedKeys(realCheckedKeys); |
| | | }; |
| | | |
| | | const handleNodeClick = () => {}; |
| | | const themeInfo = computed(() => (props.olMap as OLMap).themeInfo.value); |
| | | const themeInfo = computed(() => { |
| | | const info = (props.olMap as OLMap).themeInfo.value; |
| | | // info.push({ |
| | | // id: 'group-测试', |
| | | // title: '测试', |
| | | // type: 'theme-group', |
| | | // children: [ |
| | | // { |
| | | // id: 'junction_1', |
| | | // title: '测试1', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_2', |
| | | // title: '测试2', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_3', |
| | | // title: '测试3', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_4', |
| | | // title: '测试4', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_5', |
| | | // title: '测试5', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_6', |
| | | // title: '测试6', |
| | | // type: 'theme', |
| | | // }, |
| | | // ], |
| | | // }); |
| | | |
| | | const result = info.map((item, index) => { |
| | | if (item.children) { |
| | | // index == 0 && |
| | | // item.children.push( |
| | | // ...[ |
| | | // { |
| | | // id: 'junction_1', |
| | | // title: '测试1', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_2', |
| | | // title: '测试2', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_3', |
| | | // title: '测试3', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_4', |
| | | // title: '测试4', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_5', |
| | | // title: '测试5', |
| | | // type: 'theme', |
| | | // }, |
| | | // { |
| | | // id: 'junction_6', |
| | | // title: '测试6', |
| | | // type: 'theme', |
| | | // }, |
| | | // ] |
| | | // ); |
| | | item.viewChildren = chunk(item.children, 2); |
| | | } |
| | | return item; |
| | | }); |
| | | |
| | | return result; |
| | | }); |
| | | |
| | | const styleMap: Record<string, Record<string, Style>> = { |
| | | junction: {}, |
| | | pipe: {}, |
| | | polygon: {}, |
| | | }; |
| | | const maxResolution = 1; |
| | | |
| | | const getText = function (feature, resolution) { |
| | | const oname = feature.get('oname'); |
| | | let text = `first-${oname[0]}`; |
| | | if (resolution > maxResolution) { |
| | | text = ''; |
| | | } |
| | | |
| | | // else if (type == 'hide') { |
| | | // text = ''; |
| | | // } else if (type == 'shorten') { |
| | | // text = text.trunc(12); |
| | | // } else if (type == 'wrap' && (!dom.placement || dom.placement.value != 'line')) { |
| | | // text = stringDivider(text, 16, '\n'); |
| | | // } |
| | | |
| | | return text; |
| | | }; |
| | | const createTextStyle = (feature, resolution) => { |
| | | return new Style({ |
| | | text: new Text({ |
| | | text: getText(feature, resolution), |
| | | font: '14px Arial', |
| | | fill: new Fill({ color: '#000' }), |
| | | stroke: new Stroke({ color: '#fff', width: 2 }), |
| | | offsetX: 10, // 文字相对于几何中心的水平偏移 |
| | | offsetY: 20, // 文字相对于几何中心的垂直偏移 |
| | | }), |
| | | }); |
| | | }; |
| | | |
| | | const legendList = ref([]); |
| | | const changeTheme = async (themeId: string) => { |
| | | const loadingInstance = ElLoadingService({ |
| | | text: '加载主题中...', |
| | | target: '.layout-parent', |
| | | }); |
| | | const res = await switchMapTheme({ |
| | | theme_id: themeId, |
| | | time: formatDate(new Date()), |
| | | }).finally(() => { |
| | | loadingInstance.close(); |
| | | }); |
| | | |
| | | // 加入主题之后的样式函数 |
| | | const themeLayerStyleFunc = (feature, resolution) => { |
| | | const otype = feature.get('otype'); |
| | | const oname = feature.get('oname'); |
| | | // oname 映射样式 |
| | | const onameStyleMap = res?.[`O_${otype}`] ?? {}; |
| | | const themeStyles = res?.styles ?? []; |
| | | const styleIndex = onameStyleMap[oname]?.style_id; |
| | | const themeStyle = styleIndex == null ? null : themeStyles[styleIndex]; |
| | | // if(themeStyle){ |
| | | // } |
| | | const shape = feature.getGeometry().getType(); |
| | | const styles = []; |
| | | switch (shape) { |
| | | case 'Point': |
| | | const pSize = themeStyle?.PSIZE ?? feature.get('psize'); |
| | | |
| | | const pcolor = themeStyle?.PCOLOR ?? feature.get('pcolor'); |
| | | |
| | | const pStyle = themeStyle?.PSTYLE ?? feature.get('pstyle'); |
| | | |
| | | const pointStyle = props.olMap?.getPointStyles({ |
| | | size: pSize, |
| | | color: pcolor, |
| | | style: pStyle, |
| | | }); |
| | | pointStyle && styles.push(pointStyle); |
| | | break; |
| | | case 'LineString': |
| | | const lSize = themeStyle?.LSIZE ?? feature.get('lsize'); |
| | | const lColor = themeStyle?.LCOLOR ?? feature.get('lcolor'); |
| | | const lStyle = themeStyle?.LSTYLE ?? feature.get('lstyle'); |
| | | |
| | | const pSize1 = themeStyle?.PSIZE ?? feature.get('psize'); |
| | | const pcolor1 = themeStyle?.PCOLOR ?? feature.get('pcolor'); |
| | | const pStyle1 = themeStyle?.PSTYLE ?? feature.get('pstyle'); |
| | | |
| | | const lineStyle = props.olMap?.getLineStyles({ |
| | | lsize: lSize, |
| | | lcolor: lColor, |
| | | lstyle: lStyle, |
| | | psize: pSize1, |
| | | pcolor: pcolor1, |
| | | pstyle: pStyle1, |
| | | }); |
| | | lineStyle && styles.push(...lineStyle); |
| | | |
| | | break; |
| | | case 'Polygon': |
| | | const pColor = feature.get('pcolor'); |
| | | const lSize1 = feature.get('lsize'); |
| | | const lColor1 = feature.get('lcolor'); |
| | | const lstyle1 = feature.get('lstyle'); |
| | | const polygonStyle = props.olMap?.getPolygonStyles({ |
| | | pcolor: pColor, |
| | | lsize: lSize1, |
| | | lcolor: lColor1, |
| | | lstyle: lstyle1, |
| | | }); |
| | | polygonStyle && styles.push(...polygonStyle); |
| | | |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | |
| | | // switch (otype) { |
| | | // case 'WDM_JUNCTIONS': |
| | | // const textStyle = createTextStyle(feature, resolution); |
| | | // styles.push(textStyle); |
| | | |
| | | // case 'WDM_PIPES': |
| | | // break; |
| | | // } |
| | | //#region ====================== 添加标注 ====================== |
| | | const tString = themeStyle?.TSTRING ?? feature.get('tstring'); |
| | | const tStringStyle = props.olMap?.getLabelStyles({ |
| | | textContent: tString, |
| | | resolution, |
| | | }); |
| | | tStringStyle && styles.push(tStringStyle); |
| | | //#endregion |
| | | |
| | | return styles; |
| | | }; |
| | | const allLayers = props.olMap.getAllLayers(); |
| | | for (const item of allLayers) { |
| | | if (props.olMap?.unsupportedLayers.includes(item.id)) { |
| | | continue; |
| | | } |
| | | item.model.setStyle(themeLayerStyleFunc); |
| | | } |
| | | return res?.legends ?? []; |
| | | }; |
| | | |
| | | const handleThemeChange = async (val, themeGroup) => { |
| | | const allIds = []; |
| | | for (const item of themeInfo.value) { |
| | | if (item.activeTheme) { |
| | | allIds.push(item.activeTheme); |
| | | } |
| | | } |
| | | const ids = allIds.join(','); |
| | | |
| | | if (!ids) { |
| | | const allLayers = props.olMap.getAllLayerModels(); |
| | | for (const item of allLayers) { |
| | | const originStyle = item.get('originStyle'); |
| | | originStyle && item.setStyle(originStyle); |
| | | } |
| | | return; |
| | | } |
| | | const legends = await changeTheme(ids); |
| | | legendList.value = parseLegends(legends); |
| | | }; |
| | | |
| | | watch( |
| | | () => themeInfo.value, |
| | |
| | | } |
| | | }); |
| | | // const keys = layerInfo.value.filter((item) => item.isSet).map((item) => item.id); |
| | | console.log('🚀 ~ watch keys:', keys); |
| | | checkTreeRef.value?.setCheckedKeys(keys); |
| | | } |
| | | ); |