Commit e268d6d0c39f5f384600e630570b00b43c5dd108
Merge branch 'ft' into 'main_dev'
feat(src/packages): 优化三维模型拖动时,webgl上下文丢失问题 See merge request yunteng/thingskit-view!134
Showing
24 changed files
with
193 additions
and
176 deletions
| ... | ... | @@ -111,7 +111,7 @@ export const getVideoUrl = (id: string) => |
| 111 | 111 | }) |
| 112 | 112 | |
| 113 | 113 | //获取行政区域 |
| 114 | -export const getGeoJsonMap = (code: number, level: string) => | |
| 114 | +export const getGeoJsonMap = (code: number | string, level: string) => | |
| 115 | 115 | defHttp.get({ |
| 116 | 116 | url: `${Api.GEOJSONURL}${code}/${level}` |
| 117 | 117 | }) | ... | ... |
| ... | ... | @@ -14,6 +14,26 @@ export const enum areaEnum { |
| 14 | 14 | TOWN = 'TOWN' //镇 |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | +//父级地区编码和级别接口 | |
| 18 | +export interface HistoryParentType { | |
| 19 | + adcode: string | number | |
| 20 | + level: string | |
| 21 | +} | |
| 22 | + | |
| 23 | +//数据源接口 | |
| 24 | +export interface dataPointI { | |
| 25 | + name: string | |
| 26 | + value: number[] | |
| 27 | + adcode: number | |
| 28 | + height: number | |
| 29 | + itemStyle: { | |
| 30 | + color: string | |
| 31 | + opacity: number | |
| 32 | + borderWidth: number | |
| 33 | + borderColor: string | |
| 34 | + } | |
| 35 | +} | |
| 36 | + | |
| 17 | 37 | export const includes = [] |
| 18 | 38 | |
| 19 | 39 | export const option = { |
| ... | ... | @@ -41,6 +61,9 @@ export const option = { |
| 41 | 61 | emphasis: { |
| 42 | 62 | label: { |
| 43 | 63 | show: true, |
| 64 | + formatter: function (params: Recordable) { | |
| 65 | + return params.data.name ? params.data.name : ' ' | |
| 66 | + }, | |
| 44 | 67 | textStyle: { |
| 45 | 68 | color: '#000', |
| 46 | 69 | fontSize: 14 |
| ... | ... | @@ -59,6 +82,9 @@ export const option = { |
| 59 | 82 | regionHeight: 3, |
| 60 | 83 | label: { |
| 61 | 84 | show: true, |
| 85 | + formatter: function (params: Recordable) { | |
| 86 | + return params.data.name ? params.data.name : ' ' | |
| 87 | + }, | |
| 62 | 88 | textStyle: { |
| 63 | 89 | color: '#fff', |
| 64 | 90 | fontSize: 14 | ... | ... |
| ... | ... | @@ -12,7 +12,7 @@ import { onMounted, ref, nextTick, PropType, toRefs, watch, reactive } from 'vue |
| 12 | 12 | import * as echarts from 'echarts' |
| 13 | 13 | import { registerMap } from 'echarts/core' |
| 14 | 14 | import 'echarts-gl' |
| 15 | -import config, { areaEnum } from './config' | |
| 15 | +import config, { areaEnum, dataPointI, HistoryParentType } from './config' | |
| 16 | 16 | import { getGeoJsonMap } from '@/api/external/common' |
| 17 | 17 | import dataMaps from './data.json' |
| 18 | 18 | |
| ... | ... | @@ -73,28 +73,26 @@ props.chartConfig.option = { |
| 73 | 73 | |
| 74 | 74 | //地图点击返回 |
| 75 | 75 | const watchAdcode = async () => { |
| 76 | + stopWatch() | |
| 76 | 77 | if (props.chartConfig.option.drillingIn) { |
| 77 | 78 | //如果是从右边配置里设置的,比如点击四川省,然后点击返回 |
| 78 | 79 | const savePopParent = saveHistoryParent.value.pop() |
| 79 | - let saveAdcode: any = savePopParent?.adcode | |
| 80 | - saveLevelStr.level = savePopParent?.level as string | |
| 80 | + let saveAdcode = savePopParent?.adcode as string | number | |
| 81 | + saveLevelStr.level = savePopParent?.level | |
| 81 | 82 | if (!savePopParent) { |
| 82 | 83 | saveAdcode = getParentAdcode(props.chartConfig.option.mapRegion.adcode) |
| 83 | - saveLevelStr.level = (regionMapParentArea as any)[props.chartConfig.option.mapRegion.saveSelect.levelStr] | |
| 84 | + saveLevelStr.level = (regionMapParentArea as Recordable)[props.chartConfig.option.mapRegion.saveSelect.levelStr] | |
| 84 | 85 | } |
| 85 | 86 | if (saveAdcode === 0) { |
| 86 | 87 | saveAdcode = 'china' |
| 87 | 88 | saveLevelStr.level = 'COUNTRY' |
| 88 | 89 | } |
| 89 | - await getGeojson(saveAdcode) | |
| 90 | + const exist = await getGeojson(saveAdcode) | |
| 90 | 91 | const adcode = saveAdcode === 100000 ? 'china' : saveAdcode |
| 91 | - props.chartConfig.option.geo3D.map = adcode | |
| 92 | - props.chartConfig.option.series.forEach((item: any) => { | |
| 93 | - if (item.type === 'map3D') item.map = adcode | |
| 94 | - item.data = props.chartConfig.option.dataset | |
| 95 | - }) | |
| 96 | - handleSetOption(chartInstance.value, props.chartConfig.option) | |
| 97 | - handleDataPoint(adcode) | |
| 92 | + if (exist) { | |
| 93 | + //fix解决点击下钻返回后页面为空问题 | |
| 94 | + props.chartConfig.option.mapRegion.adcode = adcode | |
| 95 | + } | |
| 98 | 96 | } |
| 99 | 97 | } |
| 100 | 98 | |
| ... | ... | @@ -106,46 +104,40 @@ const regionMapParentArea = { |
| 106 | 104 | } |
| 107 | 105 | |
| 108 | 106 | //地图点击 |
| 109 | -const handleMap3DClick = async (params: any) => { | |
| 107 | +const handleMap3DClick = async (params: Recordable) => { | |
| 110 | 108 | if (props.chartConfig.option.drillingIn) { |
| 111 | 109 | const { name } = params |
| 112 | - saveGeojson.value?.features.forEach((item: any) => { | |
| 110 | + saveGeojson.value?.features.forEach((item: Recordable) => { | |
| 113 | 111 | if (item.properties.name === name) { |
| 114 | 112 | const level = item.properties.level.toUpperCase() |
| 115 | 113 | const adcode = item.properties.adcode |
| 116 | 114 | if (level === 'DISTRICT') return |
| 117 | - if(String(adcode).startsWith('15') && level===areaEnum.CITY) return | |
| 115 | + if (String(adcode).startsWith('15') && level === areaEnum.CITY) return | |
| 118 | 116 | props.chartConfig.option.mapRegion.adcode = adcode |
| 119 | 117 | saveLevelStr.level = level |
| 120 | 118 | handleDataPoint(adcode) |
| 121 | 119 | saveHistoryParent.value.push({ |
| 122 | 120 | adcode: item.properties.parent.adcode, |
| 123 | - level: (regionMapParentArea as any)[level] | |
| 121 | + level: (regionMapParentArea as Recordable)[level] | |
| 124 | 122 | }) |
| 125 | 123 | } |
| 126 | 124 | }) |
| 127 | 125 | } |
| 128 | 126 | } |
| 129 | 127 | |
| 130 | -const saveGeojson: any = ref({}) // 保存geojson | |
| 128 | +const saveGeojson: Recordable = ref({}) // 保存geojson | |
| 131 | 129 | |
| 132 | 130 | const chinaDefaultRegionId = ref(100000) //如果是china则adcode为100000 |
| 133 | 131 | |
| 134 | -const saveLevelStr = reactive({ | |
| 135 | - // 地区级别 | |
| 136 | - level: '' | |
| 132 | +// 保存地区级别 | |
| 133 | +const saveLevelStr = reactive<{ level: string | undefined }>({ | |
| 134 | + level: '' | |
| 137 | 135 | }) |
| 138 | 136 | |
| 139 | -//父级地区编码和级别接口 | |
| 140 | -interface HistoryParentType { | |
| 141 | - adcode: number | |
| 142 | - level: string | |
| 143 | -} | |
| 144 | - | |
| 145 | 137 | const saveHistoryParent = ref<HistoryParentType[]>([]) |
| 146 | 138 | |
| 147 | 139 | //动态注册地图 |
| 148 | -const getGeojson = (regionId: any) => { | |
| 140 | +const getGeojson = (regionId: number | string) => { | |
| 149 | 141 | try { |
| 150 | 142 | return new Promise<boolean>(resolve => { |
| 151 | 143 | const { levelStr } = props.chartConfig.option.mapRegion.saveSelect //右侧配置项获取的行政级别 |
| ... | ... | @@ -171,11 +163,10 @@ const getGeojson = (regionId: any) => { |
| 171 | 163 | } |
| 172 | 164 | } |
| 173 | 165 | |
| 174 | - | |
| 175 | 166 | //传adcode 获取上级 |
| 176 | 167 | const getParentAdcode = (adcode: number) => { |
| 177 | 168 | let adcodeNum = 100000 |
| 178 | - saveGeojson.value?.features.forEach((item: any) => { | |
| 169 | + saveGeojson.value?.features.forEach((item: Recordable) => { | |
| 179 | 170 | if (item.properties.adcode === adcode) { |
| 180 | 171 | adcodeNum = item.properties.parent.adcode |
| 181 | 172 | } |
| ... | ... | @@ -200,13 +191,13 @@ const initMap = async () => { |
| 200 | 191 | await nextTick().then(() => { |
| 201 | 192 | handleSetOption(chartInstance.value, props.chartConfig.option) |
| 202 | 193 | }) |
| 203 | - chartInstance.value.on('click', (e: any) => { | |
| 194 | + chartInstance.value.on('click', (e: Recordable) => { | |
| 204 | 195 | handleMap3DClick(e) |
| 205 | 196 | }) |
| 206 | 197 | } |
| 207 | 198 | |
| 208 | 199 | // 手动触发渲染 |
| 209 | -const handleSetOption = (instance: any, option: any) => { | |
| 200 | +const handleSetOption = (instance: any, option: Recordable) => { | |
| 210 | 201 | if (!instance) return |
| 211 | 202 | try { |
| 212 | 203 | instance.clear() |
| ... | ... | @@ -221,22 +212,22 @@ onMounted(() => { |
| 221 | 212 | }) |
| 222 | 213 | |
| 223 | 214 | //处理数据标点 |
| 224 | -const handleDataPoint = (newData: any) => { | |
| 215 | +const handleDataPoint = (newData: string | number) => { | |
| 225 | 216 | if (newData === 'china') { |
| 226 | 217 | props.chartConfig.option.dataset = dataMaps |
| 227 | 218 | } else { |
| 228 | - props.chartConfig.option.dataset = dataMaps.filter((item: any) => item.adcode === newData) | |
| 219 | + props.chartConfig.option.dataset = dataMaps.filter((item: dataPointI) => item.adcode === newData) | |
| 229 | 220 | } |
| 230 | 221 | } |
| 231 | 222 | |
| 232 | 223 | //监听地图展示区域发生变化 |
| 233 | 224 | watch( |
| 234 | 225 | () => `${props.chartConfig.option.mapRegion.adcode}`, |
| 235 | - async (newData: any) => { | |
| 226 | + async (newData: string | number) => { | |
| 236 | 227 | try { |
| 237 | 228 | await getGeojson(newData) |
| 238 | 229 | props.chartConfig.option.geo3D.map = newData |
| 239 | - props.chartConfig.option.series.forEach((item: any) => { | |
| 230 | + props.chartConfig.option.series.forEach((item: Recordable) => { | |
| 240 | 231 | if (item.type === 'map3D') { |
| 241 | 232 | item.map = newData |
| 242 | 233 | item.data = props.chartConfig.option.dataset |
| ... | ... | @@ -254,7 +245,7 @@ watch( |
| 254 | 245 | ) |
| 255 | 246 | |
| 256 | 247 | // 监听地图右侧配置项变化 |
| 257 | -watch( | |
| 248 | +const stopWatch = watch( | |
| 258 | 249 | props.chartConfig.option, |
| 259 | 250 | async newData => { |
| 260 | 251 | try { |
| ... | ... | @@ -273,7 +264,7 @@ watch( |
| 273 | 264 | () => props.chartConfig.option.dataset, |
| 274 | 265 | newData => { |
| 275 | 266 | try { |
| 276 | - props.chartConfig.option.series.forEach((item: any) => { | |
| 267 | + props.chartConfig.option.series.forEach((item: Recordable) => { | |
| 277 | 268 | if (item.type === 'map3D') { |
| 278 | 269 | item.data = newData |
| 279 | 270 | } | ... | ... |
| ... | ... | @@ -129,7 +129,7 @@ const loadList = async () => { |
| 129 | 129 | } |
| 130 | 130 | |
| 131 | 131 | const handleSubmit = () => { |
| 132 | - searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any | |
| 132 | + // searchParams.deviceProfileIds = [searchParams.deviceProfileIds] as any | |
| 133 | 133 | emit('searchParams', searchPage, searchParams) |
| 134 | 134 | handleCancel() |
| 135 | 135 | } | ... | ... |
| ... | ... | @@ -88,10 +88,11 @@ export const option = { |
| 88 | 88 | amapLon: 104.108689, |
| 89 | 89 | amapLat: 30.66176, |
| 90 | 90 | amapZindex: 11, |
| 91 | - typeMarker: '', | |
| 91 | + iconMarker: '1.png', | |
| 92 | 92 | mpBorderConfig: { |
| 93 | - value: 'Border01', | |
| 93 | + value: 'Border01' | |
| 94 | 94 | }, |
| 95 | + bgColor: 'rgba(255, 255, 255, 0.1)', | |
| 95 | 96 | marker: { |
| 96 | 97 | fillColor: '#E98984FF', |
| 97 | 98 | fillOpacity: 0.5, | ... | ... |
| ... | ... | @@ -81,8 +81,8 @@ |
| 81 | 81 | size="small" |
| 82 | 82 | placeholder="请选择您要使用的图标" |
| 83 | 83 | style="width: 250px" |
| 84 | - :value="typeMarkerValue" | |
| 85 | - :options="typeMarkerOptions" | |
| 84 | + :value="iconMarkerValue" | |
| 85 | + :options="iconMarkerOptions" | |
| 86 | 86 | :render-label="renderOption" |
| 87 | 87 | clearable |
| 88 | 88 | filterable |
| ... | ... | @@ -92,16 +92,6 @@ |
| 92 | 92 | </setting-item-box> |
| 93 | 93 | <setting-item-box name="弹窗选择" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> |
| 94 | 94 | <setting-item name="弹窗选择"> |
| 95 | - <!-- <NSelect | |
| 96 | - size="small" | |
| 97 | - placeholder="请选择您要使用的弹窗" | |
| 98 | - style="width: 250px" | |
| 99 | - :value="mapSelectBorderValue" | |
| 100 | - :options="mapSelectBorderOption.option" | |
| 101 | - @update:value="mapSelectBorderHandle" | |
| 102 | - clearable | |
| 103 | - filterable | |
| 104 | - /> --> | |
| 105 | 95 | <NSelect |
| 106 | 96 | size="small" |
| 107 | 97 | placeholder="请选择您要使用的弹窗" |
| ... | ... | @@ -115,11 +105,16 @@ |
| 115 | 105 | /> |
| 116 | 106 | </setting-item> |
| 117 | 107 | </setting-item-box> |
| 108 | + <setting-item-box name="弹窗背景" v-if="optionData.mapOptions.mapMarkerType === MarkerEnum.MARKER"> | |
| 109 | + <div style="width: 10vw"> | |
| 110 | + <n-color-picker :modes="['rgb']" v-model:value="optionData.mapOptions.bgColor" size="small"></n-color-picker> | |
| 111 | + </div> | |
| 112 | + </setting-item-box> | |
| 118 | 113 | </collapse-item> |
| 119 | 114 | </template> |
| 120 | 115 | |
| 121 | 116 | <script setup lang="ts"> |
| 122 | -import { PropType, ref, computed, h, onMounted, reactive } from 'vue' | |
| 117 | +import { PropType, ref, computed, h, onMounted } from 'vue' | |
| 123 | 118 | import { option, MarkerEnum, ThemeEnum, LangEnum, ViewModeEnum, FeaturesEnum } from './config' |
| 124 | 119 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 125 | 120 | import { NEllipsis, NImage, NSelect, NSpace, SelectOption } from 'naive-ui' |
| ... | ... | @@ -234,7 +229,7 @@ const featuresOptions = [ |
| 234 | 229 | const MarkerOptions = [ |
| 235 | 230 | // { |
| 236 | 231 | // value: MarkerEnum.CIRCLE_MARKER, |
| 237 | - // label: '圆形标点' | |
| 232 | + // label: '圆形标点' //在地图里点击无效,所以注释,有需要自行打开即可 | |
| 238 | 233 | // }, |
| 239 | 234 | { |
| 240 | 235 | value: MarkerEnum.MARKER, |
| ... | ... | @@ -246,17 +241,7 @@ const MarkerOptions = [ |
| 246 | 241 | } |
| 247 | 242 | ] |
| 248 | 243 | |
| 249 | -onMounted(() => { | |
| 250 | - props.optionData.mapOptions.typeMarker = getMarkerImagePath(props.optionData.mapOptions.typeMarker || 'position1.png') | |
| 251 | - typeMarkerValue.value = props.optionData.mapOptions.typeMarker || 'position1.png' | |
| 252 | - mapSelectBorderValue.value = | |
| 253 | - `${props.optionData.mapOptions.mpBorderConfig.value.toLocaleLowerCase()}.png` || 'border01.png' | |
| 254 | -}) | |
| 255 | - | |
| 256 | -const typeMarkerValue = ref<string | null>('position1.png') | |
| 257 | - | |
| 258 | -const mapSelectBorderValue = ref<string | null>('border01.png') | |
| 259 | - | |
| 244 | +/**通用函数封装 */ | |
| 260 | 245 | const isHref = (url: string) => { |
| 261 | 246 | try { |
| 262 | 247 | new URL(url) |
| ... | ... | @@ -265,81 +250,86 @@ const isHref = (url: string) => { |
| 265 | 250 | return false |
| 266 | 251 | } |
| 267 | 252 | } |
| 253 | +const renderCommonOption = (option: SelectOption, src: string) => { | |
| 254 | + return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | |
| 255 | + h(NImage, { | |
| 256 | + width: 25, | |
| 257 | + src, | |
| 258 | + previewDisabled: true, | |
| 259 | + style: { height: '25px' } | |
| 260 | + } as Recordable), | |
| 261 | + h(NEllipsis, null, () => option.label) | |
| 262 | + ]) | |
| 263 | +} | |
| 264 | +const getImagePath = (path: string, name: string) => { | |
| 265 | + return isHref(name) ? name : new URL(`${path}/${name}`, import.meta.url).href | |
| 266 | +} | |
| 267 | +/** */ | |
| 268 | + | |
| 269 | +/** 图标选择 */ | |
| 270 | +const iconMarkerValue = ref<string | null>('1.png') | |
| 268 | 271 | |
| 269 | -// import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 | |
| 270 | -const typeMarkerOptions = computed(() => { | |
| 272 | +// import.meta.glob 这里没有封装,不能通过传值传进去 | |
| 273 | +const iconMarkerOptions = computed(() => { | |
| 271 | 274 | const pathList = import.meta.glob('./images/marker/*') |
| 272 | 275 | return Object.keys(pathList).map(item => { |
| 273 | 276 | const imgName = item.split('/').at(-1) |
| 274 | 277 | return { |
| 275 | 278 | label: imgName, |
| 276 | 279 | value: imgName |
| 277 | - } as SelectOption | |
| 280 | + } | |
| 278 | 281 | }) |
| 279 | 282 | }) |
| 280 | 283 | // |
| 281 | 284 | |
| 282 | -const getMarkerImagePath = (name: string) => { | |
| 283 | - return isHref(name) ? name : new URL(`./images/marker/${name}`, import.meta.url).href | |
| 284 | -} | |
| 285 | +const getMarkerImagePath = (name: string) => getImagePath('./images/marker', name) | |
| 285 | 286 | |
| 286 | -const renderOption = (option: SelectOption) => { | |
| 287 | - return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | |
| 288 | - h(NImage, { | |
| 289 | - width: 25, | |
| 290 | - src: getMarkerImagePath(option.value as string), | |
| 291 | - previewDisabled: true, | |
| 292 | - style: { height: '25px' } | |
| 293 | - } as Recordable), | |
| 294 | - h(NEllipsis, null, () => option.label) | |
| 295 | - ]) | |
| 296 | -} | |
| 287 | +const renderOption = (option: SelectOption) => renderCommonOption(option, getMarkerImagePath(option.value as string)) | |
| 297 | 288 | |
| 298 | 289 | const selectHandle = (value: string) => { |
| 299 | - typeMarkerValue.value = value | |
| 300 | - props.optionData.mapOptions.typeMarker = getMarkerImagePath(value) | |
| 290 | + iconMarkerValue.value = value | |
| 291 | + props.optionData.mapOptions.iconMarker = getMarkerImagePath(value) | |
| 301 | 292 | } |
| 293 | +/** */ | |
| 294 | + | |
| 295 | +/** 弹窗选择 */ | |
| 296 | +const mapSelectBorderValue = ref<string | null>('border01.png') | |
| 302 | 297 | |
| 303 | 298 | const needBorder = ['border01.png', 'border02.png', 'border03.png', 'border05.png', 'border07.png'] |
| 304 | 299 | |
| 305 | -// import.meta.glob 这个不能封装,必须是字符串,不能通过传值传进去 | |
| 300 | +// import.meta.glob 这里没有封装,不能通过传值传进去 | |
| 306 | 301 | const mapBorderOptions = computed(() => { |
| 307 | 302 | const pathList = import.meta.glob('../../../../../../assets/images/chart/decorates/*') |
| 308 | 303 | return Object.keys(pathList) |
| 309 | 304 | .map(item => { |
| 310 | 305 | const imgName = item.split('/').at(-1) as string |
| 311 | - if (needBorder.includes(imgName)) { | |
| 312 | - return { | |
| 313 | - label: imgName, | |
| 314 | - value: imgName | |
| 315 | - } as SelectOption | |
| 316 | - } | |
| 306 | + if (!needBorder.includes(imgName)) return | |
| 307 | + return { | |
| 308 | + label: imgName, | |
| 309 | + value: imgName | |
| 310 | + } as SelectOption | |
| 317 | 311 | }) |
| 318 | 312 | .filter(Boolean) as SelectOption[] |
| 319 | 313 | }) |
| 320 | 314 | // |
| 321 | 315 | |
| 322 | -const getMapBorderImagePath = (name: string) => { | |
| 323 | - return isHref(name) ? name : new URL(`../../../../../../assets/images/chart/decorates/${name}`, import.meta.url).href | |
| 324 | -} | |
| 316 | +const getMapBorderImagePath = (name: string) => getImagePath('../../../../../../assets/images/chart/decorates', name) | |
| 325 | 317 | |
| 326 | -const renderMapBorderOption = (option: SelectOption) => { | |
| 327 | - return h(NSpace, { justify: 'space-between', style: 'padding: 0 15px; height: 28px; line-height: 28px;' }, () => [ | |
| 328 | - h(NImage, { | |
| 329 | - width: 25, | |
| 330 | - src: getMapBorderImagePath(option.value as string), | |
| 331 | - previewDisabled: true, | |
| 332 | - style: { height: '25px' } | |
| 333 | - } as Recordable), | |
| 334 | - h(NEllipsis, null, () => option.label) | |
| 335 | - ]) | |
| 336 | -} | |
| 318 | +const renderMapBorderOption = (option: SelectOption) => | |
| 319 | + renderCommonOption(option, getMapBorderImagePath(option.value as string)) | |
| 337 | 320 | |
| 338 | 321 | const selectMapBorderHandle = (value: string) => { |
| 339 | 322 | mapSelectBorderValue.value = value |
| 340 | 323 | const toLowerValue = value.toLocaleLowerCase() |
| 341 | 324 | ;(props.optionData.mapOptions.mpBorderConfig as BaseSelectBorderIF) = { |
| 342 | - value: toLowerValue[0]?.toUpperCase() + toLowerValue?.substr(1)?.split('.')[0] | |
| 343 | - } | |
| 325 | + value: toLowerValue[0]?.toUpperCase() + toLowerValue?.substring(1)?.split('.')[0] | |
| 326 | + } //这里首字母转大写,动态引入时匹配对应目录也是大写字母开头,即components\Decorates\Borders\Border01 | |
| 344 | 327 | } |
| 328 | +/** */ | |
| 329 | + | |
| 330 | +onMounted(() => { | |
| 331 | + iconMarkerValue.value = props.optionData.mapOptions.iconMarker?.split('/')?.at(-1) as string | |
| 332 | + props.optionData.mapOptions.iconMarker = getMarkerImagePath(iconMarkerValue.value) | |
| 333 | + mapSelectBorderValue.value = `${props.optionData.mapOptions.mpBorderConfig.value?.toLocaleLowerCase()}.png` | |
| 334 | +}) | |
| 345 | 335 | </script> | ... | ... |
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/1.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position1.png
1.24 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/2.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position2.png
1.4 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/3.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position3.png
904 Bytes
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/4.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position4.png
1.02 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/5.png
renamed from
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position5.png
970 Bytes
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position10.png
deleted
100644 → 0
1.2 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position11.png
deleted
100644 → 0
1.12 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position12.png
deleted
100644 → 0
1.93 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position13.png
deleted
100644 → 0
1.43 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position6.png
deleted
100644 → 0
1.51 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position7.png
deleted
100644 → 0
1.3 KB
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position8.png
deleted
100644 → 0
897 Bytes
src/packages/components/external/Charts/Maps/OverrideMapAmap/images/marker/position9.png
deleted
100644 → 0
1.61 KB
| 1 | 1 | <template> |
| 2 | 2 | <div @mouseenter="handleMouseenter" @mouseleave="handleMouseleave" class="chart-amap" ref="vChartRef"> |
| 3 | - <div v-show="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div> | |
| 4 | - <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box> | |
| 3 | + <!-- <div v-show="showSearchBox" @click.stop="handleOpenSearchBox" class="search-box"></div> | |
| 4 | + <search-box :modelShow="modelShow" @searchParams="handleSearchParams" @closeDrawer="handleCloseDrawer"></search-box> --> | |
| 5 | 5 | </div> |
| 6 | 6 | </template> |
| 7 | 7 | |
| ... | ... | @@ -16,7 +16,7 @@ import { isArray } from '@/utils' |
| 16 | 16 | import djh from './images/djh.png' |
| 17 | 17 | import online from './images/online.png' |
| 18 | 18 | import lx1 from './images/lx1.png' |
| 19 | -import positionImg from './images/marker/position1.png' | |
| 19 | +import onLineImg from './images/marker/3.png' | |
| 20 | 20 | import { getDeviceActiveTime, getDeviceList } from '@/api/external/common/index' |
| 21 | 21 | import dayjs from 'dayjs' |
| 22 | 22 | import SearchBox from './components/SearchBox.vue' |
| ... | ... | @@ -32,7 +32,7 @@ const modelShow = ref(false) |
| 32 | 32 | |
| 33 | 33 | const showSearchBox = ref(false) |
| 34 | 34 | |
| 35 | -let { | |
| 35 | +const { | |
| 36 | 36 | amapKey, |
| 37 | 37 | amapStyleKey, |
| 38 | 38 | amapLon, |
| ... | ... | @@ -46,8 +46,9 @@ let { |
| 46 | 46 | pitch, |
| 47 | 47 | skyColor, |
| 48 | 48 | marker, |
| 49 | - typeMarker, | |
| 50 | - mpBorderConfig | |
| 49 | + iconMarker, | |
| 50 | + mpBorderConfig, | |
| 51 | + bgColor | |
| 51 | 52 | } = toRefs(props.chartConfig.option.mapOptions) |
| 52 | 53 | |
| 53 | 54 | //官方没有高德地图api的ts,所以类型全用的any |
| ... | ... | @@ -104,7 +105,7 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 104 | 105 | const res = await getDeviceActiveTime(tbDeviceId) //查询设备最后离线时间 |
| 105 | 106 | let { lastUpdateTs } = res[0] |
| 106 | 107 | const lastUpdateFormatTs = dayjs(lastUpdateTs).format('YYYY-MM-DD HH:mm:ss') |
| 107 | - //render方式渲染小组件里的边框组件 | |
| 108 | + //以render方式渲染小组件里的边框组件 | |
| 108 | 109 | const BorderInstance = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/index.vue`) |
| 109 | 110 | const config = await import(`../../../../Decorates/Borders/${mpBorderConfig.value.value}/config.ts`) |
| 110 | 111 | const BorderConfigInstance = new config.default() |
| ... | ... | @@ -132,7 +133,7 @@ const createInfoWindow = async (extraInfo: dataExtraInfoType) => { |
| 132 | 133 | </div> |
| 133 | 134 | <div style="color:white;">所属组织:${organizationDTO.name}</div> |
| 134 | 135 | <div style="margin-top:6px;color:white">接入协议:${deviceProfile.transportType}</div> |
| 135 | - <div style="margin-top:6px;color:white"> | |
| 136 | + <div style="margin-top:6px;color:white;width:15vw;text-overflow: ellipsis;overflow: hidden; word-break: break-all;white-space: nowrap;"> | |
| 136 | 137 | 设备位置:${!deviceInfo.address ? '该设备暂无地理位置' : deviceInfo.address} |
| 137 | 138 | </div> |
| 138 | 139 | <div style="margin-top:6px;color:white"> |
| ... | ... | @@ -158,10 +159,15 @@ const handleCloseDrawer = () => (modelShow.value = false) |
| 158 | 159 | |
| 159 | 160 | const handleSearchParams = async (searchPage: any, params: any) => { |
| 160 | 161 | try { |
| 162 | + Object.keys(params).forEach(item => { | |
| 163 | + if (!params[item]) Reflect.deleteProperty(params, item) | |
| 164 | + }) | |
| 161 | 165 | const { items } = await getDeviceList(searchPage, params) |
| 162 | 166 | const values = filterDevice(items) |
| 163 | 167 | if (!values) return |
| 164 | - dataHandle(values) | |
| 168 | + setTimeout(() => { | |
| 169 | + dataHandle(values) | |
| 170 | + }, 1000) | |
| 165 | 171 | } finally { |
| 166 | 172 | handleCloseDrawer() |
| 167 | 173 | } |
| ... | ... | @@ -198,10 +204,11 @@ const dataHandle = (newData: dataJsonType) => { |
| 198 | 204 | // 记录新标记 |
| 199 | 205 | if (mapMarkerType.value === MarkerEnum.MARKER) { |
| 200 | 206 | newData.markers.forEach((markerItem: dataJsonMarkersType) => { |
| 207 | + const { deviceState } = markerItem.extraInfo | |
| 201 | 208 | const markerInstance = new AMapIns.Marker({ |
| 202 | 209 | position: [markerItem.position[0], markerItem.position[1]], |
| 203 | 210 | offset: new AMapIns.Pixel(-13, -30), |
| 204 | - icon: typeMarker.value || positionImg | |
| 211 | + icon: deviceState === 'ONLINE' ? onLineImg : iconMarker.value | |
| 205 | 212 | }) |
| 206 | 213 | // markers.push(markerInstance) 原作者这种方式添加,属于JS API 1.4.8版本的 |
| 207 | 214 | // markerInstance.setMap(mapIns) |
| ... | ... | @@ -255,7 +262,9 @@ watch( |
| 255 | 262 | // 预览 |
| 256 | 263 | useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { |
| 257 | 264 | stopWatch() |
| 258 | - dataHandle(newData) | |
| 265 | + setTimeout(() => { | |
| 266 | + dataHandle(newData) | |
| 267 | + }, 1000) | |
| 259 | 268 | }) |
| 260 | 269 | </script> |
| 261 | 270 | |
| ... | ... | @@ -271,15 +280,9 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => { |
| 271 | 280 | } |
| 272 | 281 | .amap-info-content .go-border-box { |
| 273 | 282 | position: absolute; |
| 274 | - transform: scale(0.7); | |
| 283 | + transform: scale(0.68); | |
| 275 | 284 | top: -10px; |
| 276 | - /* opacity: 0,8; */ | |
| 277 | - /* background-color: rgba(0, 0, 0, 0.1); */ | |
| 278 | - background-color:rgba(255,255,255,0.1) | |
| 279 | -} | |
| 280 | -.amap-info-content .go-border-box svg { | |
| 281 | - /* background-color: rgba(0, 0, 0, 0.1); */ | |
| 282 | - /* background-color: rgba(255, 255, 255, 0.8); */ | |
| 285 | + background-color: v-bind(bgColor); | |
| 283 | 286 | } |
| 284 | 287 | </style> |
| 285 | 288 | <style lang="scss" scoped> | ... | ... |
| ... | ... | @@ -5,7 +5,7 @@ import cloneDeep from 'lodash/cloneDeep' |
| 5 | 5 | import { chartInitConfig } from '@/settings/designSetting' |
| 6 | 6 | |
| 7 | 7 | export const option = { |
| 8 | - dataset: [new URL('/src/assets/external/three/test.obj', import.meta.url).href],//三维数据源 | |
| 8 | + dataset: [new URL('/src/assets/external/three/test.obj', import.meta.url).href], //三维数据源 | |
| 9 | 9 | backgroundColor: '', //场景背景色 |
| 10 | 10 | backgroundAlpha: 0, //场景透明度 |
| 11 | 11 | enableDamping: false, //是否启用阻尼 |
| ... | ... | @@ -18,7 +18,9 @@ export const option = { |
| 18 | 18 | */ |
| 19 | 19 | outputEncoding: 'liner', |
| 20 | 20 | clearScene: false, //是否清空场景内容 |
| 21 | - lights: [//灯光为数组,type 为 环境光(AmbientLight) | 方向光(DirectionalLight) | 点光(PointLight) | 半球光(HemisphereLight) | |
| 21 | + lightInput: 1, | |
| 22 | + lights: [ | |
| 23 | + //灯光为数组,type 为 环境光(AmbientLight) | 方向光(DirectionalLight) | 点光(PointLight) | 半球光(HemisphereLight) | |
| 22 | 24 | { |
| 23 | 25 | type: 'AmbientLight', |
| 24 | 26 | label: '环境光(只有颜色)', |
| ... | ... | @@ -54,38 +56,41 @@ export const option = { |
| 54 | 56 | size: 1, |
| 55 | 57 | show: false |
| 56 | 58 | }, |
| 57 | - position: [//模型位置 | |
| 59 | + position: [ | |
| 60 | + //模型位置 | |
| 58 | 61 | { |
| 59 | 62 | x: 0, |
| 60 | 63 | y: 0, |
| 61 | 64 | z: 0 |
| 62 | 65 | } |
| 63 | 66 | ], |
| 64 | - rotation: [//模型旋转 | |
| 67 | + rotation: [ | |
| 68 | + //模型旋转 | |
| 65 | 69 | { |
| 66 | 70 | x: 0, |
| 67 | 71 | y: 0, |
| 68 | 72 | z: 0 |
| 69 | 73 | } |
| 70 | 74 | ], |
| 71 | - showFps:false,//是否显示fps | |
| 72 | - labels:[ //添加图片/文字标签,暂且支持文字 | |
| 75 | + showFps: false, //是否显示fps | |
| 76 | + labels: [ | |
| 77 | + //添加图片/文字标签,暂且支持文字 | |
| 73 | 78 | { |
| 74 | - image: "", | |
| 75 | - text: "", | |
| 79 | + image: '', | |
| 80 | + text: '', | |
| 76 | 81 | textStyle: { |
| 77 | - fontFamily: "Arial", | |
| 82 | + fontFamily: 'Arial', | |
| 78 | 83 | fontSize: 18, |
| 79 | - fontWeight: "normal", | |
| 84 | + fontWeight: 'normal', | |
| 80 | 85 | lineHeight: 1, |
| 81 | - color: "#ffffff", | |
| 86 | + color: '#ffffff', | |
| 82 | 87 | borderWidth: 8, |
| 83 | 88 | borderRadius: 4, |
| 84 | - borderColor: "rgba(0,0,0,1)", | |
| 85 | - backgroundColor: "rgba(0, 0, 0, 1)" | |
| 89 | + borderColor: 'rgba(0,0,0,1)', | |
| 90 | + backgroundColor: 'rgba(0, 0, 0, 1)' | |
| 86 | 91 | }, |
| 87 | - position: {x:0, y:0, z:0}, | |
| 88 | - scale:{x:1, y:1, z:0}, | |
| 92 | + position: { x: 0, y: 0, z: 0 }, | |
| 93 | + scale: { x: 1, y: 1, z: 0 }, | |
| 89 | 94 | sid: null |
| 90 | 95 | } |
| 91 | 96 | ] | ... | ... |
| ... | ... | @@ -11,15 +11,15 @@ |
| 11 | 11 | <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" /> |
| 12 | 12 | </setting-item> |
| 13 | 13 | </setting-item-box> |
| 14 | - <setting-item-box name="上传文件"> | |
| 14 | + <setting-item-box name="上传文件"> | |
| 15 | 15 | <setting-item> |
| 16 | 16 | <FileUpload |
| 17 | - :max="100" | |
| 18 | - :fileList="optionData.dataset" | |
| 19 | - :threeSupportFileFormat="threeSupportFileFormat" | |
| 20 | - :singleFileType="singleFileTypeNotMtl" | |
| 21 | - @fileStaticUri="handleFileStaticUri" | |
| 22 | - /> | |
| 17 | + :max="100" | |
| 18 | + :fileList="optionData.dataset" | |
| 19 | + :threeSupportFileFormat="threeSupportFileFormat" | |
| 20 | + :singleFileType="singleFileTypeNotMtl" | |
| 21 | + @fileStaticUri="handleFileStaticUri" | |
| 22 | + /> | |
| 23 | 23 | </setting-item> |
| 24 | 24 | </setting-item-box> |
| 25 | 25 | <setting-item-box :alone="true"> |
| ... | ... | @@ -78,8 +78,13 @@ |
| 78 | 78 | </template> |
| 79 | 79 | </setting-item> |
| 80 | 80 | </setting-item-box> |
| 81 | + <setting-item-box name="灯光选择"> | |
| 82 | + <setting-item name="灯光选择((0,环境光),(1,方向光),(2,点光),(3,半球光))"> | |
| 83 | + <n-input-number :min="0" :max="3" v-model:value="optionData.lightInput" size="small" /> | |
| 84 | + </setting-item> | |
| 85 | + </setting-item-box> | |
| 81 | 86 | <setting-item-box name="灯光配置"> |
| 82 | - <setting-item v-for="(item, index) in optionData.lights" :name="item.label" :key="index"> | |
| 87 | + <setting-item v-for="(item, index) in [optionData.lights[optionData.lightInput]]" :name="item.label" :key="index"> | |
| 83 | 88 | <n-color-picker |
| 84 | 89 | v-if="!includeHemisphereLight.includes(item.type)" |
| 85 | 90 | size="small" |
| ... | ... | @@ -180,15 +185,9 @@ |
| 180 | 185 | <SettingItem v-if="optionData.enableDamping" name="阻尼值"> |
| 181 | 186 | <n-input-number v-model:value="optionData.dampingFactor" :min="0" :max="1" size="small"></n-input-number> |
| 182 | 187 | </SettingItem> |
| 183 | - <SettingItem name="启用动画"> | |
| 184 | - <n-switch v-model:value="optionData.autoPlay" size="small" /> | |
| 185 | - </SettingItem> | |
| 186 | - <SettingItem name="输出编码"> | |
| 188 | + <SettingItem name="输出编码,可取值为 liner 或 sRGB。linear 是 LinearEncoding 线性编码, sRGB 即 sRGBEncoding rgb 模式编码(sRGBEncoding 能更好的还原材质颜色)"> | |
| 187 | 189 | <n-select v-model:value="optionData.outputEncoding" size="small" :options="encodinghList"></n-select> |
| 188 | 190 | </SettingItem> |
| 189 | - <SettingItem name="是否清空场景"> | |
| 190 | - <n-switch @change="handleChange" v-model:value="optionData.clearScene" size="small" /> | |
| 191 | - </SettingItem> | |
| 192 | 191 | </setting-item-box> |
| 193 | 192 | </collapse-item> |
| 194 | 193 | </template> |
| ... | ... | @@ -253,10 +252,6 @@ const encodinghList = [ |
| 253 | 252 | { label: 'sRGB ', value: 'sRGB ' } |
| 254 | 253 | ] |
| 255 | 254 | |
| 256 | -const handleChange = (e: boolean) => { | |
| 257 | - if (e) props.optionData.dataset = [''] | |
| 258 | -} | |
| 259 | - | |
| 260 | 255 | const handleFileStaticUri = (value: UploadFileInfo[]) => { |
| 261 | 256 | props.optionData.dataset = value.map(item => item?.url)?.filter(Boolean) as any |
| 262 | 257 | if (Array.isArray(props.optionData.dataset) && props.optionData.dataset.length === 0) { | ... | ... |
| 1 | 1 | <template> |
| 2 | - <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : ''}"> | |
| 3 | - <div v-if="useDetectWebGLContext()"> | |
| 2 | + <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : '' }"> | |
| 3 | + <div> | |
| 4 | 4 | <vue3dLoader |
| 5 | 5 | ref="vue3dLoaderRef" |
| 6 | 6 | :webGLRendererOptions="webGLRendererOptions" |
| ... | ... | @@ -20,7 +20,7 @@ |
| 20 | 20 | @load="onLoad" |
| 21 | 21 | :position="position" |
| 22 | 22 | :rotation="rotation" |
| 23 | - :lights="lights" | |
| 23 | + :lights="[lights[lightInput]]" | |
| 24 | 24 | :showFps="showFps" |
| 25 | 25 | :labels="labels" |
| 26 | 26 | /> |
| ... | ... | @@ -29,15 +29,13 @@ |
| 29 | 29 | <n-progress type="line" :color="themeColor" :percentage="process" :indicator-placement="'inside'" processing /> |
| 30 | 30 | </div> |
| 31 | 31 | </div> |
| 32 | - <div v-else>您的浏览器不支持WebGL!</div> | |
| 33 | 32 | </div> |
| 34 | 33 | </template> |
| 35 | 34 | <script setup lang="ts"> |
| 36 | -import { PropType, toRefs, ref, nextTick, computed, watch } from 'vue' | |
| 35 | +import { PropType, toRefs, ref, nextTick, computed } from 'vue' | |
| 37 | 36 | import { CreateComponentType } from '@/packages/index.d' |
| 38 | 37 | import { vue3dLoader } from 'vue-3d-loader' |
| 39 | 38 | import { useDesignStore } from '@/store/modules/designStore/designStore' |
| 40 | -import { useDetectWebGLContext } from '@/utils/external/useSupportWebGL' | |
| 41 | 39 | |
| 42 | 40 | const designStore = useDesignStore() |
| 43 | 41 | |
| ... | ... | @@ -54,7 +52,6 @@ const themeColor = computed(() => { |
| 54 | 52 | |
| 55 | 53 | const vue3dLoaderRef = ref(null) |
| 56 | 54 | |
| 57 | -//threejs配置 | |
| 58 | 55 | const webGLRendererOptions = { |
| 59 | 56 | alpha: true, // 透明 |
| 60 | 57 | antialias: true, // 抗锯齿 |
| ... | ... | @@ -96,13 +93,9 @@ const { |
| 96 | 93 | rotation, |
| 97 | 94 | lights, |
| 98 | 95 | showFps, |
| 99 | - labels | |
| 100 | -} = toRefs(props.chartConfig.option) as any | |
| 101 | - | |
| 102 | -watch(dataset, (newData: string) => { | |
| 103 | - //dateset为空则清除场景 | |
| 104 | - if(!newData) clearScene.value=true | |
| 105 | -}) | |
| 96 | + labels, | |
| 97 | + lightInput | |
| 98 | +} = toRefs(props.chartConfig.option) as any | |
| 106 | 99 | </script> |
| 107 | 100 | |
| 108 | 101 | <style lang="scss" scoped> | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <div> |
| 3 | 3 | <n-tree |
| 4 | + ref="nTreeRef" | |
| 4 | 5 | :accordion="treeConfig.accordion" |
| 5 | 6 | :checkable="treeConfig.checkable" |
| 6 | 7 | :default-expand-all="treeConfig.defaultExpandAll" |
| ... | ... | @@ -11,17 +12,20 @@ |
| 11 | 12 | label-field="name" |
| 12 | 13 | children-field="children" |
| 13 | 14 | @update:selected-keys="onClick" |
| 15 | + @update:checked-keys="onClick" | |
| 16 | + :checked-keys="checkedKeys" | |
| 14 | 17 | /> |
| 15 | 18 | </div> |
| 16 | 19 | </template> |
| 17 | 20 | |
| 18 | 21 | <script setup lang="ts"> |
| 19 | -import { PropType, toRefs } from 'vue' | |
| 22 | +import { PropType, toRefs, ref } from 'vue' | |
| 20 | 23 | import { CreateComponentType } from '@/packages/index.d' |
| 21 | 24 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 22 | 25 | import { useChartInteract } from '@/hooks/external/useChartSelectInteract.hook' |
| 23 | 26 | import { InteractEventOn } from '@/enums/eventEnum' |
| 24 | 27 | import { ComponentInteractParamsEnum } from './interact' |
| 28 | +import { NTree } from 'naive-ui' | |
| 25 | 29 | |
| 26 | 30 | const props = defineProps({ |
| 27 | 31 | chartConfig: { |
| ... | ... | @@ -32,7 +36,16 @@ const props = defineProps({ |
| 32 | 36 | |
| 33 | 37 | const { dataset, treeConfig } = toRefs(props.chartConfig.option) |
| 34 | 38 | |
| 39 | +const nTreeRef = ref<null | InstanceType<typeof NTree>>(null) | |
| 40 | + | |
| 41 | +const checkedKeys = ref([]) | |
| 42 | + | |
| 35 | 43 | const onClick = (v: string[]) => { |
| 44 | + // nTreeRef.value?.selectedKeys(v) | |
| 45 | + console.log(nTreeRef.value) | |
| 46 | + console.log(v) | |
| 47 | + // nTreeRef.value?.onUpdateCheckedKeys(v) | |
| 48 | + if (Array.isArray(v) && v.length == 0) return | |
| 36 | 49 | useChartInteract( |
| 37 | 50 | props.chartConfig, |
| 38 | 51 | useChartEditStore, | ... | ... |