Commit a71eee39 authored by 罗超's avatar 罗超

完善二次贝塞尔曲线,完善标记可以被批量处理等功能

parent 90e9cee1
......@@ -76,6 +76,7 @@ declare module 'vue' {
Select: typeof import('./src/components/Select.vue')['default']
SelectGroup: typeof import('./src/components/SelectGroup.vue')['default']
Slider: typeof import('./src/components/Slider.vue')['default']
SpLineAttributes: typeof import('./src/components/Maps/MapAttributes/SpLineAttributes.vue')['default']
Switch: typeof import('./src/components/Switch.vue')['default']
SymbolContent: typeof import('./src/components/LaTeXEditor/SymbolContent.vue')['default']
Tabs: typeof import('./src/components/Tabs.vue')['default']
......
......@@ -9,8 +9,11 @@
<IconDelete class="btn" style="font-size: 12px;" @click="removeLine" />
</div>
</div>
<div class="text-right">
<el-checkbox v-model="updateAllLineStatus" @click.stop="selectAllLineHandler">更新所有线条</el-checkbox>
<div class="row items-center">
<div class="col q-ml-md">
<el-button @click="changeToLineHandler" link size="mini" type="primary" style="font-size: 11px;">变更为双头曲线</el-button>
</div>
<el-checkbox v-model="updateAllLineStatus" @click.stop="selectAllLineHandler">同步更新</el-checkbox>
</div>
<div class="attr-items flex items-center">
<i class="icon-arrow-thickness q-mr-lg" title="粗细" style="margin-top: 5px;"></i>
......@@ -50,7 +53,7 @@
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import { inject, reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor,Triangle} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
......@@ -58,6 +61,7 @@ import { watch } from 'vue';
const mapStore = useMapStore()
const { current,lineSeries } = storeToRefs(mapStore)
const updateAllLineStatus=ref(false)
const lineTransfromSpine = inject("linetoSpline",(spline:any)=>{})
const attrs = reactive({
arrow:-1
} as any)
......@@ -167,6 +171,22 @@ const setAttribute = ()=>{
const removeLine = ()=>{
mapStore.removeCurrentLine()
}
const changeToLineHandler = ()=>{
let changes:number[]=[]
for (let i = 0; i < lineSeries.value?.mapLines.length!; i++) {
let element = lineSeries.value?.mapLines.getIndex(i);
if(element?.uid==current.value.uid || updateAllLineStatus.value){
changes.push(i)
element?.hide()
lineTransfromSpine(element)
}
}
changes.forEach(x=>{
lineSeries.value?.mapLines.removeIndex(x)
})
mapStore.setCurrent(null)
}
setAttribute()
watch(()=>current.value,()=>{
......
......@@ -9,6 +9,9 @@
<IconDelete class="btn" style="font-size: 12px;" @click="removeMark" />
</div>
</div>
<div class="text-right">
<el-checkbox v-model="updateStatus">同步更新</el-checkbox>
</div>
<div class="attr-items flex items-center q-mt-lg">
<div style="font-size: 11px;width:50px;">定位</div>
<el-select v-model="attrs.selectObj" filterable size="mini" remote reserve-keyword placeholder="输入关键字" @change="setLocationHandler" :remote-method="searchPoiInfoHandler" :loading="searchLoading">
......@@ -40,7 +43,7 @@
<div class="attr-items flex items-center q-mt-md">
<div style="font-size: 11px;width:50px;">对齐</div>
<el-select size="mini" v-model="attrs.fontAlign" @change="val=>calcLabelLocal(undefined,val as number)" placeholder="请选择">
<el-select size="mini" v-model="attrs.fontAlign" @change="val=>setAlignChangeHandler(val as number)" placeholder="请选择">
<el-option v-for="item in positions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
......@@ -85,7 +88,7 @@
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import { inject, reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor,Label} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
......@@ -102,6 +105,8 @@ const attrs = reactive({
selectObj:''
} as any)
const options = ref<{label:string,value:string,center:number[]}[]>([])
const changeMarkStyleToCurrent = inject("changeMarkStyleToCurrent",()=>{})
const updateStatus = ref(false)
const searchLoading = ref(false)
const icons = ref<string[]>(['icon-arrow-left','icon-arrow-right','icon-arrow-leftright','icon-arrow-middle'])
const positions = ref<{label:string,value:number}[]>([{label:'底部',value:0},{label:'顶部',value:1},{label:'靠右',value:2},{label:'靠左',value:3}])
......@@ -132,10 +137,6 @@ const updateStrokeHandler = (val:any) =>{
attrs.strokeOpacity = tinycolor(val).getAlpha()
updateLineStyle(current.value)
}
const selectAllLineHandler = (val:any)=>{
//console.log(val, updateAllLineStatus.value)
}
const updateLineStyle = (lineObj:any)=>{
let chi = lineObj.mapImages.getIndex(0).children.getIndex(0)
let tempChi = lineObj.mapImages.template.children.getIndex(0)
......@@ -152,6 +153,7 @@ const updateLineStyle = (lineObj:any)=>{
tempChi.scale = attrs.scale
tempChi.fillOpacity = attrs.fillOpacity
tempChi.fill = am4coreColor(tinycolor(attrs.fill).toHexString())
asyncAllMarkHandler()
}
const createLabel = ()=>{
......@@ -179,7 +181,7 @@ const createLabel = ()=>{
const calcLabelLocal = (label?:Label,align:number=0) =>{
if(!label){
label = getCurrentLabel()
align = align==-1? label!.marginBottom as number:align
align = align==-1 && label ? label!.marginBottom as number:align
}
if(label){
......@@ -237,6 +239,7 @@ const changeLabelStatus = (val:boolean)=>{
} else {
current.value.mapImages.removeIndex(1)
}
asyncAllMarkHandler()
}
const updateLabFontSizeHandler = (val:number) =>{
let lab = getCurrentLabel()
......@@ -244,6 +247,7 @@ const updateLabFontSizeHandler = (val:number) =>{
lab.fontSize=val
setTimeout(() => {
calcLabelLocal(undefined,-1)
asyncAllMarkHandler()
}, 500);
}
}
......@@ -252,13 +256,19 @@ const setLabelTextHandler = (val:string)=>{
if(lab){
lab.text=val
calcLabelLocal(lab,lab.marginBottom)
asyncAllMarkHandler()
}
}
const setAlignChangeHandler = (val:number)=>{
calcLabelLocal(undefined,val)
asyncAllMarkHandler()
}
const setLabelColorHandler = (val:string)=>{
let lab = getCurrentLabel()
if(lab){
lab.fill = am4coreColor(tinycolor(val).toHex8String())
attrs.fontColor = tinycolor(val).toHex8String()
asyncAllMarkHandler()
}
}
......@@ -294,6 +304,12 @@ const setAttribute = ()=>{
const removeMark = ()=>{
mapStore.setRemoveMark(current.value)
}
const asyncAllMarkHandler = ()=>{
if(updateStatus.value){
changeMarkStyleToCurrent()
}
}
setAttribute()
watch(()=>current.value,()=>{
......
<template>
<div class="attr-box">
<div class="box-header">
<div class="row items-center attr-header">
<img class="drag-icon"
src="https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1704767732000_329.webp"
alt="">
<div class="title col">双曲线设置</div>
<IconDelete class="btn" style="font-size: 12px;" @click="removeLine" />
</div>
</div>
<div class="row items-center">
<div class="col q-ml-md">
<el-button @click="changeToLineHandler" link size="mini" type="primary" style="font-size: 11px;">变更为线条</el-button>
</div>
<el-checkbox v-model="updateAllLineStatus" @click.stop="selectAllLineHandler">同步更新</el-checkbox>
</div>
<div class="attr-items flex items-center">
<i class="icon-arrow-thickness q-mr-lg" title="粗细" style="margin-top: 5px;"></i>
<Slider class="filter-slider" :max="50" :min="1" :step="1" :value="attrs.strokeWidth"
@update:value="value => updateFilter(0, value as number)" />
</div>
<div class="attr-items flex items-center">
<i class="icon-arrow-dasharray q-mr-lg" title="虚线" style="margin-top: 5px;"></i>
<Slider class="filter-slider" :max="10" :min="0" :step="0.1" :value="attrs.strokeDasharray"
@update:value="value => updateFilter(1, value as number)" />
</div>
<div class="attr-items flex items-center">
<IconRotationHorizontal class="q-mr-lg" title="横向节点" style="margin-top: 5px;" />
<Slider class="filter-slider" :max="10" :min="-10" :step="0.1" :value="attrs.tensionX"
@update:value="value => updateFilter(2, value as number)" />
</div>
<div class="attr-items flex items-center">
<IconRotationVertical class="q-mr-lg" title="Y轴弯曲" style="margin-top: 5px;font-weight: bold;" />
<Slider class="filter-slider" :max="10" :min="-10" :step="0.1" :value="attrs.tensionY"
@update:value="value => updateFilter(3, value as number)" />
</div>
<div class="attr-items flex grey-bg items-center" style="border-bottom: 1px;padding: 0px;">
<div class="col arrow-type text-center" v-for="(x,i) in icons" @click="updateLinePoint(i)" :class="{'active':attrs.arrow==i}">
<i :class="[x]"></i>
</div>
</div>
<div class="attr-items flex items-center">
<div class="color-box q-mr-lg">
<Popover trigger="mouseenter" class="full-height full-width">
<template #content>
<ColorPicker :modelValue="attrs.stroke" @update:modelValue="value => updateFillHandler(value)"/>
</template>
<div class="full-height full-width" :style="{backgroundColor:attrs.stroke}"></div>
</Popover>
</div>
<div>{{ attrs.stroke }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { inject, reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor,Triangle} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
const mapStore = useMapStore()
const { current,splineSeries,lineSeries } = storeToRefs(mapStore)
const updateAllLineStatus=ref(false)
const splineTransfromLine = inject("splinetoline",(spline:any)=>{})
const attrs = reactive({
arrow:-1
} as any)
const icons = ref<string[]>(['icon-arrow-left','icon-arrow-right','icon-arrow-leftright','icon-arrow-middle'])
const updateFilter = (t: number, val: number) => {
switch (t) {
case 0:
attrs.strokeWidth = val
break;
case 1:
attrs.strokeDasharray = val
break;
case 2:
attrs.tensionX = val
break;
case 3:
attrs.tensionY = val
break;
default:
break;
}
updated()
}
const updateFillHandler = (val:any) =>{
attrs.stroke = val
attrs.opacity = tinycolor(val).getAlpha()
updated()
}
const selectAllLineHandler = (val:any)=>{
//console.log(val, updateAllLineStatus.value)
}
const updated =()=>{
updateLineStyle(current.value)
if(updateAllLineStatus.value && splineSeries.value?.mapLines.length!>1){
for (let i = 0; i < splineSeries.value?.mapLines.length!; i++) {
let element = splineSeries.value?.mapLines.getIndex(i);
if(element?.uid!=current.value.uid){
updateLineStyle(element)
}
}
}
updateLinePoint(attrs.arrow,true)
}
const updateLineStyle = (lineObj:any)=>{
lineObj.strokeWidth= attrs.strokeWidth
lineObj.strokeOpacity = attrs.opacity
lineObj.stroke = am4coreColor(tinycolor(attrs.stroke).toHex8String())
lineObj.children.getIndex(0).tensionX = attrs.tensionX
lineObj.children.getIndex(0).tensionY = attrs.tensionY
lineObj.strokeDasharray = `${attrs.strokeDasharray},${attrs.strokeDasharray}`
console.log(attrs.strokeDasharray)
}
const updateLinePoint = (t:number,isReload:boolean=false)=>{
if(t==attrs.arrow && !isReload) t=-1
console.log(t)
for (let i = 0; i < splineSeries.value?.mapLines.length!; i++) {
let element = splineSeries.value?.mapLines.getIndex(i);
if(element?.uid==current.value.uid || updateAllLineStatus.value){
element!.lineObjects.clear()
if(t==0){
createPoint(0,element)
} else if(t==1){
createPoint(1,element)
} else if(t==2){
createPoint(0,element)
createPoint(1,element)
} else if(t==3){
createPoint(0.5,element)
}
}
}
attrs.arrow=t
}
const createPoint = (point:number,lineObj:any)=>{
console.log(lineObj.lineObjects)
let rotation = point == 0?270:90
let bullet = lineObj.lineObjects.create();
bullet.position = point;
bullet.nonScaling = true;
bullet.width = attrs.strokeWidth + 10
bullet.height = attrs.strokeWidth + 10
bullet.strokeWidth = 0
var tria = bullet.createChild(Triangle);
tria.width = attrs.strokeWidth + 10
tria.height = attrs.strokeWidth + 10
tria.fill = am4coreColor(tinycolor(attrs.stroke).toHexString());
tria.strokeWidth = point;
tria.rotation = rotation
tria.x = point == 0? -tria.width:tria.width
tria.y = point == 0? tria.width/2:-tria.width/2
}
const setAttribute = ()=>{
attrs.strokeWidth=current.value.strokeWidth
attrs.stroke=tinycolor(current.value.stroke.hex).setAlpha(current.value.strokeOpacity).toHex8String()
attrs.opacity=current.value.strokeOpacity
attrs.tensionX = current.value.children.getIndex(0).tensionX
attrs.tensionY = current.value.children.getIndex(0).tensionY
attrs.strokeDasharray=current.value.strokeDasharray && current.value.strokeDasharray!=''?parseInt(current.value.strokeDasharray.split(',')[0]):0
if(current.value.lineObjects.length>0){
if(current.value.lineObjects.length==2) attrs.arrow=2
else{
let p = current.value.lineObjects.getIndex(0).position
attrs.arrow = p==0.5?3:p
}
}
}
const removeLine = ()=>{
splineSeries.value?.mapLines.removeValue(current.value)
mapStore.setCurrent(null)
}
const changeToLineHandler = ()=>{
let changes:number[]=[]
for (let i = 0; i < splineSeries.value?.mapLines.length!; i++) {
let element = splineSeries.value?.mapLines.getIndex(i);
if(element?.uid==current.value.uid || updateAllLineStatus.value){
changes.push(i)
element?.hide()
splineTransfromLine(element)
}
}
changes.forEach(x=>{
splineSeries.value?.mapLines.removeIndex(x)
})
mapStore.setCurrent(null)
}
setAttribute()
watch(()=>current.value,()=>{
setAttribute()
})
</script>
\ No newline at end of file
......@@ -4,6 +4,7 @@
<MarkAttributes v-if="current && current._className=='MapImageSeries'"></MarkAttributes>
<TextAttributes v-if="current && current._className=='Label'"></TextAttributes>
<CountriesAttrbutes v-if="current && current=='Country'"></CountriesAttrbutes>
<SpLineAttributes v-if="current && current._className=='MapSpline'"></SpLineAttributes>
</template>
<script setup lang="ts">
......@@ -14,6 +15,7 @@ import AreaAttributes from './AreaAttributes.vue';
import MarkAttributes from './MarkAttributes.vue';
import TextAttributes from './TextAttributes.vue';
import CountriesAttrbutes from './CountriesAttrbutes.vue';
import SpLineAttributes from './SpLineAttributes.vue';
const mapStore = useMapStore()
const { current } = storeToRefs(mapStore)
......
......@@ -22,7 +22,7 @@
import tools from './Tools.vue'
import MapAttributes from './MapAttributes/index.vue';
import mapLine from './Line.vue'
import { onMounted, ref, watch } from 'vue';
import { onMounted, provide, ref, watch } from 'vue';
import useMap from './useMap'
import { useMapStore,useScreenStore } from '@/store/index'
import { storeToRefs } from 'pinia';
......@@ -54,8 +54,11 @@ const loadingStatus = ref(true)
const dataLoadingStatus = ref(false)
const { operaIndex,canOpera,marker } = storeToRefs(mapStore)
const { ConfigId } = storeToRefs(useScreenStore())
const { initMap,setCountry,setMarks,setLines,disposeAll,exportPng } =useMap(mapRef,loadingStatus)
const { initMap,setCountry,setMarks,setLines,disposeAll,exportPng,lineTransformSpline,splineTransformLine,changeMarkStyleToCurrent } =useMap(mapRef,loadingStatus)
const tripInfo = ref<any>()
provide("splinetoline", splineTransformLine);
provide("linetoSpline", lineTransformSpline);
provide("changeMarkStyleToCurrent",changeMarkStyleToCurrent)
watch(() => props.visible, () => {
visibleStatus.value = props.visible
......@@ -146,7 +149,7 @@ const closed = ()=>{
onMounted(()=>{
setTimeout(() => {
initMap()
ConfigId.value = 9765
//ConfigId.value = 9765
}, 2000);
})
......
......@@ -10,29 +10,31 @@ import am4geodata_data_countries from "@amcharts/amcharts4-geodata/data/countrie
import am5geodata_lang_cn_ZH from "@amcharts/amcharts4-geodata/lang/cn_ZH";
export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
export default (MapDOM: Ref<HTMLElement | undefined>, loadingStatus: Ref<boolean>) => {
const mapStore = useMapStore()
const { operaIndex,fillColor,fillType,canOpera,newLine,lineSeries,removeMark,labelSeries,execut } = storeToRefs(mapStore)
const { operaIndex, fillColor, fillType, canOpera, newLine, lineSeries, removeMark, labelSeries, execut,splineSeries } = storeToRefs(mapStore)
mapStore.setAllCountries(am5geodata_lang_cn_ZH)
const eventName = ref<string>('')
let map:am4maps.MapChart
let worldSeries:am4maps.MapPolygonSeries
let countrySeries:am4maps.MapPolygonSeries
let map: am4maps.MapChart
let worldSeries: am4maps.MapPolygonSeries
let countrySeries: am4maps.MapPolygonSeries
let countryPolygon: am4maps.MapPolygon
let back:am4core.ZoomOutButton
let addButton:am4core.Button
const CountriesKeys=ref<string[]>([])
let back: am4core.ZoomOutButton
let addButton: am4core.Button
const CountriesKeys = ref<string[]>([])
const initMap = ()=>{
loadingStatus.value=true
const initMap = () => {
loadingStatus.value = true
am4core.useTheme(am4themes_animated);
map = am4core.create(MapDOM.value, am4maps.MapChart)
map.geodata = am4geodata_worldHigh
map.geodataNames = am5geodata_lang_cn_ZH
map.projection = new am4maps.projections.Mercator();
map.events.on('hit',()=>{
if(!eventName.value || eventName.value=='') mapStore.setCurrent(null)
map.events.on('hit', () => {
if (!eventName.value || eventName.value == '') mapStore.setCurrent(null)
eventName.value = ''
})
map.zoomControl = new am4maps.ZoomControl();
......@@ -41,11 +43,11 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
worldSeries = map.series.push(new am4maps.MapPolygonSeries());
worldSeries.exclude = ["AQ"];
if(CountriesKeys.value && CountriesKeys.value.length>0){
if (CountriesKeys.value && CountriesKeys.value.length > 0) {
worldSeries.include = CountriesKeys.value
}
worldSeries.useGeodata = true;
worldSeries.clickable=true
worldSeries.clickable = true
let polygonTemplate = worldSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}";
......@@ -53,13 +55,13 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
polygonTemplate.fill = am4core.color(`#818181`);
polygonTemplate.stroke = am4core.color(`#ffffff`);
polygonTemplate.strokeWidth = 0.1
polygonTemplate.events.on("hit",(e)=>{
polygonTemplate.events.on("hit", (e) => {
createCountry(e)
})
worldSeries.events.on('ready',(e)=>{
worldSeries.events.on('ready', (e) => {
console.log('ready.........')
loadingStatus.value=false
loadingStatus.value = false
})
initCountry()
......@@ -67,9 +69,12 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
createAddButton()
initLineSeries()
initLabelSeries()
initSplineSeries()
}
const initCountry =()=>{
const initCountry = () => {
countrySeries = map.series.push(new am4maps.MapPolygonSeries());
countrySeries.useGeodata = true;
countrySeries.hide();
......@@ -84,70 +89,70 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
countryPolygon.fill = am4core.color(`#818181`);
countryPolygon.stroke = am4core.color(`#ffffff`);
countryPolygon.strokeWidth = 0.1
countryPolygon.strokeDasharray='20,20'
countryPolygon.strokeDasharray = '20,20'
countryPolygon.events.on("hit", (e) => {
countryPolygon.events.on("hit",(e)=>{
if(operaIndex.value==1) fillColorHandler(e)
else if(operaIndex.value==5) createMarkImageHandler(e)
else if(operaIndex.value==3) createLabel(e)
if (operaIndex.value == 1) fillColorHandler(e)
else if (operaIndex.value == 5) createMarkImageHandler(e)
else if (operaIndex.value == 3) createLabel(e)
else {
eventName.value='MapPolygon'
eventName.value = 'MapPolygon'
mapStore.setCurrent(e.target)
}
})
}
const disposeAll = () =>{
const disposeAll = () => {
map.disposeChildren()
map.dispose()
}
const exportPng = ():Promise<string> => {
loadingStatus.value=true
const exportPng = (): Promise<string> => {
loadingStatus.value = true
let tempBack = back.isShowing
let tempAdd = addButton.isShowing
back.hide()
addButton.hide()
map.zoomControl.hide()
map.exporting.backgroundColor=am4core.color(`#00000000`)
return new Promise((resolve)=>{
map.exporting.backgroundColor = am4core.color(`#00000000`)
return new Promise((resolve) => {
setTimeout(() => {
map.exporting.getImage("png").then(r=>{
map.exporting.getImage("png").then(r => {
map.zoomControl.show()
if(tempBack) back.show()
if(tempAdd) addButton.show()
loadingStatus.value=false
if (tempBack) back.show()
if (tempAdd) addButton.show()
loadingStatus.value = false
resolve(r)
}).catch((e)=>{
}).catch((e) => {
map.zoomControl.show()
if(tempBack) back.show()
if(tempAdd) addButton.show()
loadingStatus.value=false
if (tempBack) back.show()
if (tempAdd) addButton.show()
loadingStatus.value = false
resolve('')
})
}, 1000)
})
}
const createMarkImageHandler = (e:any)=>{
const createMarkImageHandler = (e: any) => {
let markerPath = mapStore.getMarker
let imageSeries = map.series.push(new am4maps.MapImageSeries())
if(markerPath == 'circle'){
if (markerPath == 'circle') {
let circle = imageSeries.mapImages.template.createChild(am4core.Circle);
circle.radius = 10;
circle.fill = am4core.color(fillColor.value.realColor.substring(0,7));
circle.fill = am4core.color(fillColor.value.realColor.substring(0, 7));
circle.fillOpacity = fillColor.value.opacity
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.scale = 1;
}else{
} else {
var marker1 = imageSeries.mapImages.template.createChild(am4core.Circle);
marker1.path = markerPath
marker1.fill = am4core.color(fillColor.value.realColor.substring(0,7));
marker1.fill = am4core.color(fillColor.value.realColor.substring(0, 7));
marker1.fillOpacity = fillColor.value.opacity
marker1.stroke = am4core.color("#FFFFFF");
marker1.strokeWidth = 0;
......@@ -159,48 +164,48 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
imageSeries.mapImages.template.propertyFields.latitude = "latitude";
imageSeries.mapImages.template.propertyFields.longitude = "longitude";
imageSeries.data = [
map.svgPointToGeo({x:e.svgPoint.x,y:e.svgPoint.y})
map.svgPointToGeo({ x: e.svgPoint.x, y: e.svgPoint.y })
]
imageSeries.events.on("dragstart",()=>{
imageSeries.events.on("dragstart", () => {
map.panBehavior = 'none'
})
imageSeries.events.on("dragstop",()=>{
imageSeries.events.on("dragstop", () => {
map.panBehavior = "move"
})
imageSeries.events.on("hit",(e)=>{
imageSeries.events.on("hit", (e) => {
eventName.value = 'MapImageSeries'
mapStore.setCurrent(e.target)
})
eventName.value = 'MapImageSeries'
mapStore.setCurrent(imageSeries)
}
const setMarks = (marks:{lat:number,lng:number,name:string}[])=>{
const setMarks = (marks: { lat: number, lng: number, name: string }[]) => {
for (let i = 0; i < marks.length; i++) {
const x = marks[i];
let imageSeries = map.series.push(new am4maps.MapImageSeries())
let circle = imageSeries.mapImages.template.createChild(am4core.Circle);
circle.radius = 10;
circle.fill = am4core.color(fillColor.value.realColor.substring(0,7));
circle.fill = am4core.color(fillColor.value.realColor.substring(0, 7));
circle.fillOpacity = fillColor.value.opacity
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.scale = 0.6;
imageSeries.mapImages.template.nonScaling = true;
imageSeries.mapImages.template.propertyFields.latitude = "latitude";
imageSeries.mapImages.template.propertyFields.longitude = "longitude";
imageSeries.data = [{latitude:x.lat,longitude:x.lng}]
imageSeries.events.on("dragstart",()=>{
imageSeries.data = [{ latitude: x.lat, longitude: x.lng }]
imageSeries.events.on("dragstart", () => {
map.panBehavior = 'none'
})
imageSeries.events.on("dragstop",()=>{
imageSeries.events.on("dragstop", () => {
map.panBehavior = "move"
})
imageSeries.events.on("hit",(e)=>{
imageSeries.events.on("hit", (e) => {
eventName.value = 'MapImageSeries'
mapStore.setCurrent(e.target)
})
......@@ -209,13 +214,13 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
let labelChildren = imageSeries.mapImages.create();
labelChildren.latitude = x.lat;
labelChildren.longitude = x.lng;
let label = labelChildren.createChild(am4core.Label)
label.horizontalCenter = "middle";
label.fontSize = 13;
label.fontFamily = 'pingfangr'
label.fill = am4core.color(fillColor.value.realColor)
label.text= x.name
label.text = x.name
// labelChildren.children.getIndex(0).hide()
// label.marginBotton=0
label.y = 6
......@@ -232,34 +237,34 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
}
}
const setLines = (vals:{latitude:number,longitude:number}[][])=>{
const setLines = (vals: { latitude: number, longitude: number }[][]) => {
for (let i = 0; i < vals.length; i++) {
const item = vals[i];
let points:am4core.IGeoPoint[] = item
let points: am4core.IGeoPoint[] = item
createLine(points)
}
}
const fillColorHandler = (e:any) =>{
if(operaIndex.value!=1) return;
if(fillType.value==2){
const fillColorHandler = (e: any) => {
if (operaIndex.value != 1) return;
if (fillType.value == 2) {
countryPolygon.polygon.fill = am4core.color(`#${fillColor.value.color.toString(16).padStart(6, '0')}`)
countryPolygon.polygon.fillOpacity = fillColor.value.opacity
}else{
} else {
var dataItem = e.target.dataItem as any;
dataItem.dataContext.colorWasSet=false
dataItem.dataContext.colorWasSet = false
e.target.polygon.fill = am4core.color(`#${fillColor.value.color.toString(16).padStart(6, '0')}`)
e.target.polygon.fillOpacity = fillColor.value.opacity
}
}
const createCountry = (ev:any) => {
const createCountry = (ev: any) => {
mapStore.clearCheckedCountries()
ev.target.series.chart.zoomToMapObject(ev.target);
var chiMap = ev.target.dataItem.dataContext.id;
if (chiMap) {
let geoUrl = am4geodata_data_countries[chiMap].find(x=>x.indexOf("High")!=-1)
let geoUrl = am4geodata_data_countries[chiMap].find(x => x.indexOf("High") != -1)
ev.target.isHover = false;
// @ts-ignore
countrySeries.geodata = null
......@@ -269,66 +274,66 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
countrySeries.geodataSource.url = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
countrySeries.geodataSource.load();
mapStore.setCheckedCountries({
id:chiMap,
name:ev.target.dataItem.dataContext.name,
url:countrySeries.geodataSource.url
id: chiMap,
name: ev.target.dataItem.dataContext.name,
url: countrySeries.geodataSource.url
})
back.show();
addButton.show()
canOpera.value=true
canOpera.value = true
}
}
const setCountry = (names:string[]) => {
const setCountry = (names: string[]) => {
mapStore.clearCheckedCountries()
let allCountries = mapStore.getAllCountries
let countriesData:any[] = []
Object.keys(allCountries!).forEach((key:string)=>{
if(names.includes(allCountries![key])){
let countriesData: any[] = []
Object.keys(allCountries!).forEach((key: string) => {
if (names.includes(allCountries![key])) {
countriesData.push({
id:key,
name:allCountries![key]
id: key,
name: allCountries![key]
});
}
})
if(countriesData && countriesData.length>0){
if(countriesData.length==1){
if (countriesData && countriesData.length > 0) {
if (countriesData.length == 1) {
let first = countriesData[0]
let geoUrl = am4geodata_data_countries[first.id].find(x=>x.indexOf("High")!=-1)
let geoUrl = am4geodata_data_countries[first.id].find(x => x.indexOf("High") != -1)
// @ts-ignore
countrySeries.geodata = null
// @ts-ignore
countrySeries.include = null
countrySeries.invalidateData()
setTimeout(() => {
try {
try {
map.zoomToMapObject(worldSeries.getPolygonById(first.id))
} catch (error) {}
} catch (error) { }
}, 500);
countrySeries.geodataSource.url = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
countrySeries.geodataSource.load();
mapStore.setCheckedCountries({
id:first.id,
name:first.name,
url:countrySeries.geodataSource.url
id: first.id,
name: first.name,
url: countrySeries.geodataSource.url
})
} else if(countriesData.length>1){
let idArray:any[] = []
} else if (countriesData.length > 1) {
let idArray: any[] = []
for (let i = 0; i < countriesData.length; i++) {
const item = countriesData[i];
let geoUrl = am4geodata_data_countries[item.id].find(x=>x.indexOf("High")!=-1)
let geoUrl = am4geodata_data_countries[item.id].find(x => x.indexOf("High") != -1)
geoUrl = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
mapStore.setCheckedCountries({
id:item.id,
name:item.name,
url:geoUrl
id: item.id,
name: item.name,
url: geoUrl
})
idArray.push(item.id)
}
countrySeries.geodata = am4geodata_worldHigh
countrySeries.include=idArray
countrySeries.include = idArray
countrySeries.invalidateData()
worldSeries.hide();
countrySeries.show();
......@@ -336,47 +341,47 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
try {
map.zoomToMapObject(countrySeries.getPolygonById(idArray![0]))
} catch (error) {
}
}, 500);
}
back.show();
addButton.show()
canOpera.value=true
canOpera.value = true
}
}
const setMulitpCountries = (payload:any,execut:number)=>{
const setMulitpCountries = (payload: any, execut: number) => {
let tempChecked = mapStore.getCheckedCountries
let id = payload.id
let idArray = tempChecked.map(x=>x.id)
if(execut==1){
let geoUrl = am4geodata_data_countries[id].find(x=>x.indexOf("High")!=-1)
let idArray = tempChecked.map(x => x.id)
if (execut == 1) {
let geoUrl = am4geodata_data_countries[id].find(x => x.indexOf("High") != -1)
geoUrl = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
mapStore.setCheckedCountries({
id:id,
name:payload.name,
url:geoUrl
id: id,
name: payload.name,
url: geoUrl
})
idArray.push(id)
}else{
} else {
let ri = idArray.indexOf(id)
if(ri!=-1) {
idArray.splice(ri,1)
tempChecked.splice(ri,1)
if (ri != -1) {
idArray.splice(ri, 1)
tempChecked.splice(ri, 1)
}
}
if (idArray.length>1) {
if (idArray.length > 1) {
countrySeries.geodata = am4geodata_worldHigh
countrySeries.include=idArray
countrySeries.include = idArray
countrySeries.invalidateData()
setTimeout(() => {
map.zoomToMapObject(countrySeries.getPolygonById(id))
}, 500);
} else if(idArray.length==1){
} else if (idArray.length == 1) {
// @ts-ignore
countrySeries.geodata = null
// @ts-ignore
......@@ -384,7 +389,7 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
countrySeries.invalidateData()
countrySeries.geodataSource.url = tempChecked[0].url
countrySeries.geodataSource.load();
} else{
} else {
worldSeries.show();
map.goHome();
countrySeries.hide();
......@@ -395,20 +400,20 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
mapStore.clearCheckedCountries()
mapStore.setCurrent(null)
}
}
const createBackButton = () =>{
const createBackButton = () => {
back = map.createChild(am4core.ZoomOutButton);
back.align = "right";
back.positionInvalid
back.margin(20,20,20,0)
back.margin(20, 20, 20, 0)
back.icon = new am4core.Sprite()
back.icon.fill = am4core.color(`#ffffff`)
back.icon.path = "M16,8 L14,8 L14,16 L10,16 L10,10 L6,10 L6,16 L2,16 L2,8 L0,8 L8,0 L16,8 Z M16,8"
back.readerTitle = '返回'
back.hide();
back.events.on("hit", function() {
back.events.on("hit", function () {
worldSeries.show();
map.goHome();
countrySeries.hide();
......@@ -421,11 +426,11 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
});
}
const createAddButton = ()=>{
const createAddButton = () => {
addButton = map.createChild(am4core.ZoomOutButton)
addButton.positionInvalid
addButton.margin(0,20,0,0)
addButton.align="right"
addButton.margin(0, 20, 0, 0)
addButton.align = "right"
addButton.y = 40
addButton.icon = new am4core.Sprite()
addButton.icon.fill = am4core.color(`#ffffff`)
......@@ -435,18 +440,18 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
addButton.cursorOverStyle = am4core.MouseCursorStyle.pointer
addButton.hide();
addButton.events.on("hit", function() {
eventName.value='Country'
addButton.events.on("hit", function () {
eventName.value = 'Country'
mapStore.setCurrent('Country')
});
}
const initLabelSeries = ()=>{
const initLabelSeries = () => {
labelSeries.value = map.series.push(new am4maps.MapImageSeries())
}
const createLabel = (e:any) => {
let point = map.svgPointToGeo({x:e.svgPoint.x,y:e.svgPoint.y})
const createLabel = (e: any) => {
let point = map.svgPointToGeo({ x: e.svgPoint.x, y: e.svgPoint.y })
let labelChildren = labelSeries.value!.mapImages.create();
labelChildren.latitude = point.latitude;
labelChildren.longitude = point.longitude;
......@@ -459,28 +464,28 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
label.nonScaling = true;
label.interactionsEnabled = false;
label.fill = am4core.color(fillColor.value.realColor)
label.text= '文字...'
label.text = '文字...'
label.cursorOverStyle = am4core.MouseCursorStyle.pointer
label.draggable = true
label.interactionsEnabled = true
label.events.on("dragstart",()=>{
label.events.on("dragstart", () => {
map.panBehavior = 'none'
})
label.events.on("dragstop",()=>{
label.events.on("dragstop", () => {
map.panBehavior = "move"
})
label.events.on("hit",(e)=>{
label.events.on("hit", (e) => {
eventName.value = 'Label'
mapStore.setCurrent(e.target)
})
eventName.value = 'Label'
mapStore.setCurrent(label)
//label.children.getIndex(0)!.text = state;
}
const initLineSeries = ()=>{
const initLineSeries = () => {
lineSeries.value = map.series.push(new am4maps.MapArcSeries());
lineSeries.value!.mapLines.template.strokeWidth = 2;
lineSeries.value!.mapLines.template.stroke = am4core.color(`#${fillColor.value.color.toString(16).padStart(6, '0')}`);
......@@ -493,20 +498,111 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
lineSeries.value!.mapLines.template.strokeLinecap = 'round'
}
const createLine = (points:am4core.IGeoPoint[]) => {
const initSplineSeries = () => {
splineSeries.value = map.series.push(new am4maps.MapSplineSeries());
if(splineSeries.value){
splineSeries.value.mapLines.template.line.tensionX = 0.6;
splineSeries.value.mapLines.template.line.tensionY = 2;
splineSeries.value.mapLines.template.strokeWidth = 2;
splineSeries.value.mapLines.template.stroke = am4core.color(`#${fillColor.value.color.toString(16).padStart(6, '0')}`);
splineSeries.value.mapLines.template.strokeOpacity = fillColor.value.opacity
splineSeries.value.mapLines.template.nonScalingStroke = true
splineSeries.value.mapLines.template.line.cursorOverStyle = am4core.MouseCursorStyle.pointer
splineSeries.value.mapLines.template.strokeLinecap = 'round'
// let pointers =[
// { "latitude": 48.856614, "longitude": 2.352222 },
// { "latitude": 40.712775, "longitude": -74.005973 }
// ]
// createSpLine(pointers)
}
}
const createSpLine = (points: am4core.IGeoPoint[]) => {
if(splineSeries.value){
let line = splineSeries.value.mapLines.create();
line.multiGeoLine = [points];
line.draggable = true
line.strokeDasharray = '0,0'
//@ts-ignore
line.children.getIndex(0).tensionY = 2
line.events.on("hit", (e) => {
console.log(e)
eventName.value = 'lineEvent'
mapStore.setCurrent(e.target)
})
line.events.on("dragstart", () => {
map.panBehavior = 'none'
})
line.events.on("dragstop", () => {
map.panBehavior = "move"
})
mapStore.setCurrent(line)
}
}
const lineTransformSpline = (line:am4maps.MapLine)=>{
let spline = splineSeries.value!.mapLines.create()
spline.multiGeoLine = line.multiGeoLine
spline.stroke = line.stroke
spline.strokeLinecap = line.strokeLinecap
spline.strokeWidth = line.strokeWidth
spline.strokeOpacity = line.strokeOpacity
spline.strokeDasharray = line.strokeDasharray
spline.draggable = true
//@ts-ignore
spline.children.getIndex(0).tensionY = 2
spline.events.on("hit", (e) => {
eventName.value = 'lineEvent'
mapStore.setCurrent(e.target)
})
spline.events.on("dragstart", () => {
map.panBehavior = 'none'
})
spline.events.on("dragstop", () => {
map.panBehavior = "move"
})
//mapStore.setCurrent(line)
}
const splineTransformLine = (spline:am4maps.MapSpline)=>{
let line = lineSeries.value!.mapLines.create()
line.multiGeoLine = spline.multiGeoLine
line.stroke = spline.stroke
line.strokeLinecap = spline.strokeLinecap
line.strokeWidth = spline.strokeWidth
line.strokeOpacity = spline.strokeOpacity
line.strokeDasharray = spline.strokeDasharray
line.draggable = true
line.events.on("hit", (e) => {
eventName.value = 'lineEvent'
mapStore.setCurrent(e.target)
})
line.events.on("dragstart", () => {
map.panBehavior = 'none'
})
line.events.on("dragstop", () => {
map.panBehavior = "move"
})
//mapStore.setCurrent(line)
}
const createLine = (points: am4core.IGeoPoint[]) => {
let line = lineSeries.value!.mapLines.create();
line.multiGeoLine = [points];
line.draggable = true
line.events.on("hit", (e)=>{
line.multiGeoLine = [points];
line.draggable = true
line.events.on("hit", (e) => {
eventName.value = 'lineEvent'
mapStore.setCurrent(e.target)
})
line.events.on("dragstart",()=>{
line.events.on("dragstart", () => {
map.panBehavior = 'none'
})
line.events.on("dragstop",()=>{
line.events.on("dragstop", () => {
map.panBehavior = "move"
})
//var tria = bullet.createChild();
......@@ -514,44 +610,150 @@ export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
mapStore.setCurrent(line)
}
const changeMarkStyleToCurrent = ()=>{
let current = mapStore.getCurrent
if(current){
let cc = current.mapImages.getIndex(0).children.getIndex(0)
let ct = current.mapImages.template.children.getIndex(0)
let cl:any = undefined
if(current.mapImages.length>1 && current.mapImages.getIndex(1).children.length>1){
cl = current.mapImages.getIndex(1).children.getIndex(1)
}
for (let i = 0; i < map.series.length; i++) {
const element = map.series.getIndex(i);
if(element && element.className=='MapImageSeries' && element.uid!=current.uid && element.data && element.data.length>0){
let mark = element as am4maps.MapImageSeries
if(mark && mark.mapImages.length>0 && mark.mapImages.getIndex(0)!.children!.length>0){
let chi = mark.mapImages!.getIndex(0)!.children.getIndex(0)
let tempChi = mark.mapImages.template.children.getIndex(0)
if(chi){
chi.strokeWidth= cc.strokeWidth
chi.strokeOpacity = cc.strokeOpacity
chi.stroke = cc.stroke
chi.scale = cc.scale
chi.fillOpacity = cc.fillOpacity
chi.fill = cc.fill
}
if(tempChi){
tempChi.strokeWidth= ct.strokeWidth
tempChi.strokeOpacity = ct.strokeOpacity
tempChi.stroke = ct.stroke
tempChi.scale = ct.scale
tempChi.fillOpacity = ct.fillOpacity
tempChi.fill = ct.fill
}
if(cl){
let lab:any = undefined
if(mark.mapImages.length>1 && mark.mapImages!.getIndex(1)!.children.length>1){
lab = mark.mapImages!.getIndex(1)!.children.getIndex(1)
}
if(!lab){
let labelChildren = mark.mapImages.create();
console.log(mark.data)
labelChildren.latitude = mark.data[0].latitude;
labelChildren.longitude = mark.data[0].longitude;
let label = labelChildren.createChild(am4core.Label)
label.horizontalCenter = "middle";
label.fontSize = 13;
label.fontFamily = 'pingfangr'
label.fill = am4core.color(fillColor.value.realColor)
label.text= '文字内容'
labelChildren.children.getIndex(0)!.hide()
lab = label
}
//lab.text = cl.text
lab.fontSize = cl.fontSize??15
lab.marginBottom = cl.marginBottom
lab.fill = cl.fill
calcLabelLocal(lab,mark)
} else {
if(mark.mapImages.length>1 && mark.mapImages!.getIndex(1)!.children.length>1){
mark.mapImages.removeIndex(1)
}
}
}
}
}
}
}
watch(() => newLine.value,(newVal:number[])=>{
let points:am4core.IGeoPoint[] = []
points.push(map.svgPointToGeo({x:newVal[0],y:newVal[1]}))
points.push(map.svgPointToGeo({x:newVal[2],y:newVal[3]}))
const calcLabelLocal = (label:am4core.Label,parent:am4maps.MapImageSeries) =>{
if(label){
let scale = parent.mapImages.template.children.getIndex(0)!.scale
//@ts-ignore
let radius = parent.mapImages.template.children.getIndex(0)!.radius
let align = label.marginBottom
radius = radius==10?10:35
let temp = radius*scale
let increment = radius==10?10:0
if(align==0){
label.y = temp
label.paddingLeft = temp
label.x = 0 - (increment*.5)
} else if(align==1){
label.y = -(label.fontSize*1.33) - increment
label.paddingLeft = temp
label.x = 0 - (increment*.5)
} else if(align==2){
label.y = ((temp-label.fontSize - increment)/2)-(label.fontSize*0.2)
label.x = ((label.fontSize*label.text.length)/2)+temp+(increment*.5)
label.paddingLeft = 0
} else if(align==3){
label.y = ((temp-label.fontSize - increment)/2)-(label.fontSize*0.2)
label.x = (-label.fontSize*label.text.length)/2 - (increment*1.5)
label.paddingLeft = 0
}
//label.marginBottom=align
}
}
watch(() => newLine.value, (newVal: number[]) => {
let points: am4core.IGeoPoint[] = []
points.push(map.svgPointToGeo({ x: newVal[0], y: newVal[1] }))
points.push(map.svgPointToGeo({ x: newVal[2], y: newVal[3] }))
createLine(points)
})
watch(() => execut.value, (newVal)=>{
watch(() => execut.value, (newVal) => {
if (newVal) {
setMulitpCountries(newVal.payload,newVal.exec)
setMulitpCountries(newVal.payload, newVal.exec)
}
})
watch(() => removeMark.value,(newVal:any)=>{
if(newVal){
watch(() => removeMark.value, (newVal: any) => {
if (newVal) {
let i = -1
for (let j = 0; j < map.series.length; j++) {
const element = map.series.getIndex(j);
if(element?.uid==newVal.uid){
i=j
if (element?.uid == newVal.uid) {
i = j
break
}
}
if(i!=-1){
if (i != -1) {
map.series.removeIndex(i)
mapStore.setCurrent(null)
removeMark.value=null
removeMark.value = null
}
}
})
return {
initMap,
setCountry,
setMarks,
setLines,
disposeAll,
exportPng
exportPng,
lineTransformSpline,
splineTransformLine,
changeMarkStyleToCurrent
}
}
\ No newline at end of file
......@@ -123,6 +123,8 @@ import {
CloseOne,
Info,
Earth,
RotationHorizontal,
RotationVertical
} from '@icon-park/vue-next'
export interface Icons {
......@@ -251,6 +253,8 @@ export const icons: Icons = {
IconCloseOne: CloseOne,
IconInfo: Info,
IconEarth: Earth,
IconRotationHorizontal: RotationHorizontal,
IconRotationVertical: RotationVertical
}
export default {
......
import { defineStore } from "pinia";
import {MapArcSeries,MapImageSeries} from "@amcharts/amcharts4/maps";
import {MapArcSeries,MapImageSeries,MapSplineSeries} from "@amcharts/amcharts4/maps";
import { Lang } from "@amcharts/amcharts4-geodata/.internal/Lang";
export interface MapFill{
......@@ -34,6 +34,7 @@ interface MapState{
checkedCountries:MapCountryInfo[],
allCountries:Lang|null,
execut:MapCountryOpera|null
splineSeries:MapSplineSeries|undefined
}
export const useMapStore = defineStore('map',{
......@@ -54,7 +55,8 @@ export const useMapStore = defineStore('map',{
labelSeries:undefined,
checkedCountries:[],
allCountries:null,
execut:null
execut:null,
splineSeries:undefined
}),
getters:{
getCurrent(state){
......@@ -104,6 +106,12 @@ export const useMapStore = defineStore('map',{
this.current = null
}
},
removeCurrentSpLine(){
if(this.splineSeries && this.current && this.current._className=='MapArc'){
this.splineSeries.mapLines.removeValue(this.current)
this.current = null
}
},
setRemoveMark(val:string) {
this.removeMark = val
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment