Commit f52d598a authored by 罗超's avatar 罗超

地图功能更新

parent 9505d0cd
......@@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Alpha: typeof import('./src/components/ColorPicker/Alpha.vue')['default']
AreaAttributes: typeof import('./src/components/Maps/MapAttributes/AreaAttributes.vue')['default']
Button: typeof import('./src/components/Button.vue')['default']
ButtonGroup: typeof import('./src/components/ButtonGroup.vue')['default']
Checkboard: typeof import('./src/components/ColorPicker/Checkboard.vue')['default']
......@@ -15,6 +16,7 @@ declare module 'vue' {
CheckboxButton: typeof import('./src/components/CheckboxButton.vue')['default']
ColorPicker: typeof import('./src/components/ColorPicker/index.vue')['default']
Contextmenu: typeof import('./src/components/Contextmenu/index.vue')['default']
CountriesAttrbutes: typeof import('./src/components/Maps/MapAttributes/CountriesAttrbutes.vue')['default']
Divider: typeof import('./src/components/Divider.vue')['default']
Drawer: typeof import('./src/components/Drawer.vue')['default']
EditableInput: typeof import('./src/components/ColorPicker/EditableInput.vue')['default']
......@@ -31,6 +33,8 @@ declare module 'vue' {
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRow: typeof import('element-plus/es')['ElRow']
......@@ -49,6 +53,11 @@ declare module 'vue' {
Index: typeof import('./src/components/PSD/Index.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
LaTeXEditor: typeof import('./src/components/LaTeXEditor/index.vue')['default']
Line: typeof import('./src/components/Maps/Line.vue')['default']
LineAttributes: typeof import('./src/components/Maps/MapAttributes/LineAttributes.vue')['default']
MapAttributes: typeof import('./src/components/Maps/MapAttributes/index.vue')['default']
Maps: typeof import('./src/components/Maps/index.vue')['default']
MarkAttributes: typeof import('./src/components/Maps/MapAttributes/MarkAttributes.vue')['default']
MenuContent: typeof import('./src/components/Contextmenu/MenuContent.vue')['default']
Message: typeof import('./src/components/Message.vue')['default']
Modal: typeof import('./src/components/Modal.vue')['default']
......@@ -66,6 +75,8 @@ declare module 'vue' {
SymbolContent: typeof import('./src/components/LaTeXEditor/SymbolContent.vue')['default']
Tabs: typeof import('./src/components/Tabs.vue')['default']
TextArea: typeof import('./src/components/TextArea.vue')['default']
TextAttributes: typeof import('./src/components/Maps/MapAttributes/TextAttributes.vue')['default']
Tools: typeof import('./src/components/Maps/Tools.vue')['default']
WritingBoard: typeof import('./src/components/WritingBoard.vue')['default']
}
export interface ComponentCustomProperties {
......
......@@ -9,6 +9,8 @@
"build:fonts": "node scripts/build-fonts"
},
"dependencies": {
"@amcharts/amcharts4": "^4.10.38",
"@amcharts/amcharts4-geodata": "^4.1.28",
"@element-plus/icons-vue": "^2.1.0",
"@icon-park/vue-next": "^1.4.2",
"@types/ali-oss": "^6.16.11",
......@@ -25,6 +27,7 @@
"file-saver": "^2.0.5",
"hfmath": "0.0.2",
"html-to-image": "^1.11.11",
"konva": "^9.3.0",
"lodash": "^4.17.21",
"md5-ts": "^0.1.6",
"mitt": "^3.0.1",
......@@ -51,6 +54,8 @@
"tinycolor2": "^1.6.0",
"tippy.js": "^6.3.7",
"vue": "^3.3.7",
"vue-konva": "^3.0.2",
"vue3-leaderline": "^1.2.11",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
......
......@@ -7,11 +7,16 @@ page {
sans-serif;
}
/* .0123456789+-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz导入设计文件基础数据绑定模板预览选择、上传图片 */
/* .0123456789+-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz导入设计文件基础数据绑定模板预览选择、上传图片地创作 */
@font-face {
font-family: "alifont";
src: url("//at.alicdn.com/wf/webfont/MQHUV6e56ce5/hnLMgZN9aCoO.woff2") format("woff2"),
url("//at.alicdn.com/wf/webfont/MQHUV6e56ce5/KFZWXS7eHHXx.woff") format("woff");
font-family: "alifont";src: url("//at.alicdn.com/wf/webfont/MQHUV6e56ce5/af1x9gjmJyCb.woff2") format("woff2"),
url("//at.alicdn.com/wf/webfont/MQHUV6e56ce5/9KYFlUm2wrb2.woff") format("woff");
font-display: swap;
}
@font-face {
font-family: "pingfangr";
src: url("https://im.oytour.com/tripfont/PingFangR.ttf") format("truetype");
font-display: swap;
}
.relative{
......
@font-face {
font-family: generalicons;
src: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1704769454000_860.woff') format("woff");
font-weight: 400;
font-style: normal;
}
[class*="icon-"]:before {
display: inline-block;
font-family: generalicons;
font-style: normal;
font-weight: 400;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-pixel-circle:before {
content: "\0041";
}
.icon-pixel-square:before {
content: "\0042";
}
.icon-pixel-diamond:before {
content: "\0043";
}
.icon-pixel-hexagon:before {
content: "\0044";
}
.icon-arrow-left:before {
content: "\0045";
}
.icon-arrow-leftright:before {
content: "\0046";
}
.icon-arrow-middle:before {
content: "\0047";
}
.icon-arrow-right:before {
content: "\0048";
}
.attr-box .icon-arrow-right:before {
content: "\0048" !important;
}
.icon-arrow-thickness:before {
content: "\0049";
}
.icon-arrow-dasharray:before {
content: "\004A";
}
.icon-arrow-arc:before {
content: "\004B";
}
.icon-prop-remove:before {
content: "\004C";
}
.icon-prop-pin:before {
content: "\004D";
}
.icon-prop-unpin:before {
content: "\004D";
display: inline-block;
height: 11px;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
overflow: hidden;
}
\ No newline at end of file
<template>
<div style="position: absolute;z-index: 39;left: 0;top:0;right: 0;bottom: 0;" @mousemove="updateLinePathHandler" @click.stop="endDrawHandler" ref="drawPanel">
<v-stage :config="configKonva">
<v-layer>
<v-line ref="line" :config="configLine"></v-line>
</v-layer>
</v-stage>
</div>
</template>
<script setup lang="ts">
import { PropType, ref } from 'vue';
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { injectKeyMapOperaIndex } from '@/types/injectKey';
const props = defineProps({
position:{
type: Object as PropType<{x:number,y:number}>,
require: true
},
parent:{
type: Object as PropType<HTMLElement>,
require: true
}
})
const emit = defineEmits<{
(event: 'success'): void
}>()
const mapStore = useMapStore()
const { fillColor,newLine } = storeToRefs(mapStore)
const configKonva = ref({
width: props.parent?.offsetWidth,
height: props.parent?.offsetHeight,
})
const configLine = ref({
points: [props.position?.x??0,props.position?.y??0, props.position?.x??0, props.position?.y??0],
stroke: `#${fillColor.value.color.toString(16).padStart(6, '0')}`,
strokeWidth: 2,
lineCap: 'round',
opacity: fillColor.value.opacity
})
const drawPanel = ref()
const canMove = ref(true)
const line = ref()
const updateLinePathHandler = (e:MouseEvent)=>{
if(!canMove.value) return
configLine.value.points[2] = e.offsetX
configLine.value.points[3] = e.offsetY
line.value.getNode().parent.batchDraw();
}
const endDrawHandler = () => {
canMove.value=false
let t = configLine.value.points
newLine.value = [t[0],t[1],t[2],t[3]]
mapStore.setOperaIndex(0)
emit('success')
}
</script>
<style scoped>
/* Add any necessary styles */
</style>
<template>
<div class="attr-box" @click.stop="(e:MouseEvent)=>e.stopPropagation()">
<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>
</div>
</div>
<div class="attr-items grey-bg" style="border-top-width: 1px; font-size: 11px;padding: 20px;">
{{ attrs.name }}
</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.fill" @update:modelValue="value => updateFillHandler(value)"/>
</template>
<div class="full-height full-width" :style="{backgroundColor:attrs.fill}"></div>
</Popover>
</div>
<div>{{ attrs.fill }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
const mapStore = useMapStore()
const { current,fillColor } = storeToRefs(mapStore)
const attrs = reactive({} as any)
const updateFillHandler = (val:any) =>{
attrs.fill = val
attrs.opacity = tinycolor(val).getAlpha()
current.value.fill = am4coreColor(tinycolor(val).toHexString())
current.value.fillOpacity = attrs.opacity
}
const setAttribute = ()=>{
let context = current.value.dataItem.dataContext
attrs.name = (context.CNTRY?context.CNTRY +' - ':'')+context.name
attrs.opacity = current.value.fillOpacity!=undefined?current.value.fillOpacity:1
attrs.fill = current.value.fill?tinycolor(current.value.fill.hex).setAlpha(attrs.opacity).toHex8String():fillColor.value.realColor
}
setAttribute()
watch(()=>current.value,()=>{
setAttribute()
})
</script>
<style>
</style>
\ No newline at end of file
<template>
<div class="attr-box" @click.stop="(e:MouseEvent)=>e.stopPropagation()">
<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>
</div>
</div>
<div class=" grey-bg squer-box row items-center">
<el-select size="large" filterable v-model="attrs.selectedCountries" placeholder="请选择">
<el-option :label="item.label" :value="item.value" :disabled="item.checked" v-for="(item, index) in attrs.countriesData" :key="index" />
</el-select>
<el-button size="large" @click="addNewCountryHandler" :disable="attrs.selectedCountries==''">添加</el-button>
</div>
<div class="grey-bg q-px-md">
<div class="row q-py-md border-top" v-for="(x,i) in checkedCountries" :key="i">
<div class="seriesNo">{{ i+1 }}</div>
<div style="font-family: pingfangr;font-size: 14px;" class="q-mx-md col">{{ x.name }}</div>
<el-button type="danger" @click="removeCountryHandler(x.id)" link><IconDelete /></el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import { watch } from 'vue';
const mapStore = useMapStore()
const { current,allCountries,checkedCountries } = storeToRefs(mapStore)
const attrs = reactive({
selectedCountries:'',
countriesData:[] as any[]
} as any)
const resolveCountries = ()=>{
attrs.countriesData=[]
if(allCountries.value){
Object.keys(allCountries.value).forEach(function(key:string){
attrs.countriesData.push({
value:key,
label:allCountries.value![key]
});
})
checkStatus()
}
}
const checkStatus = ()=>{
const idArray = checkedCountries.value.map(x=>x.id)
attrs.countriesData.forEach((x:any)=>{
x.checked = idArray.indexOf(x.value)!=-1
})
}
const addNewCountryHandler = () => {
if(attrs.selectedCountries){
mapStore.setCountriesChange({
exec:1,
payload:{
id:attrs.selectedCountries,
name:allCountries.value![attrs.selectedCountries]
}
})
attrs.selectedCountries=''
}
}
const removeCountryHandler=(id:string)=>{
mapStore.setCountriesChange({
exec:0,
payload:{
id:id,
name:allCountries.value![attrs.selectedCountries]
}
})
}
resolveCountries()
watch(()=>allCountries.value,()=>{
resolveCountries()
})
watch(()=>checkedCountries.value,()=>{
checkStatus()
})
</script>
<style>
.squer-box .el-input__wrapper,
.squer-box .el-button--large{
border-radius: 0 !important;
border-left: none !important;
}
.attr-box .seriesNo{
background-color: #000;
color: #f5f5f5;
width:20px;
height: 20px;
line-height: 20px;
font-size: 11px;
border-radius: 20px;
text-align: center;
}
.attr-box .border-top{
border-top: 1px solid rgba(204, 204, 204, .3);
}
</style>
\ No newline at end of file
<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="text-right">
<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">
<i class="icon-arrow-arc q-mr-lg" title="弯曲" style="margin-top: 5px;"></i>
<Slider class="filter-slider" :max="0.6" :min="-0.6" :step="0.1" :value="attrs.controlPointDistance"
@update:value="value => updateFilter(2, 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 { 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,lineSeries } = storeToRefs(mapStore)
const updateAllLineStatus=ref(false)
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.controlPointDistance = 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 && lineSeries.value?.mapLines.length!>1){
for (let i = 0; i < lineSeries.value?.mapLines.length!; i++) {
let element = lineSeries.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.line.controlPointDistance = attrs.controlPointDistance
lineObj.line.controlPointPosition = 0.5
lineObj.strokeDasharray = `${attrs.strokeDasharray},${attrs.strokeDasharray}`
}
const updateLinePoint = (t:number,isReload:boolean=false)=>{
if(t==attrs.arrow && !isReload) t=-1
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){
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)=>{
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.controlPointDistance=current.value.line.controlPointDistance??0
attrs.strokeDasharray=current.value.strokeDasharray && current.value.strokeDasharray!=''?parseInt(current.value.strokeDasharray.split(',')):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 = ()=>{
mapStore.removeCurrentLine()
}
setAttribute()
watch(()=>current.value,()=>{
setAttribute()
})
</script>
\ No newline at end of file
<template>
<div class="attr-box" @click.stop="(e:MouseEvent)=>e.stopImmediatePropagation()">
<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="removeMark" />
</div>
</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">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="attr-items flex items-center q-mt-lg">
<div style="font-size: 11px;width:50px;">边框</div>
<Slider class="filter-slider" :max="10" :min="0" :step="1" :value="attrs.strokeWidth"
@update:value="value => updateFilter(0, value as number)" />
</div>
<div class="attr-items flex items-center q-mt-lg">
<div style="font-size: 11px;width:50px;">大小</div>
<Slider class="filter-slider" :max="5" :min="0.1" :step="0.1" :value="attrs.scale"
@update:value="value => updateFilter(1, value as number)" />
</div>
<div class="attr-items flex items-center q-mt-lg grey-bg">
<div style="font-size: 11px; " class="col">文字</div>
<Switch :value="attrs.textEnable" @update:value="value => changeLabelStatus(value)" />
</div>
<div v-if="attrs.textEnable" class="grey-bg" style="border-top: 1px solid rgba(204, 204, 204, .3);border-bottom: 1px solid rgba(204, 204, 204, .3);">
<div class="attr-items q-mt-md grey-bg">
<el-input input-style="border: none !important; background: none !important;" v-model="attrs.fontText" :rows="3" @input="setLabelTextHandler" type="textarea" placeholder="文字..." />
</div>
<div class="attr-items flex items-center grey-bg">
<div style="font-size: 11px;width:50px;">大小</div>
<Slider class="filter-slider" :max="72" :min="12" :step="1" :value="attrs.fontSize" @update:value="value => updateLabFontSizeHandler(value as number)" />
</div>
<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-option v-for="item in positions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="attr-items flex items-center q-mt-md">
<div class="color-box q-mr-lg">
<Popover trigger="mouseenter" class="full-height full-width">
<template #content>
<ColorPicker :modelValue="attrs.fontColor" @update:modelValue="value => setLabelColorHandler(value)"/>
</template>
<div class="full-height full-width" style="border:1px solid #EEE;" :style="{backgroundColor:attrs.fontColor}"></div>
</Popover>
</div>
<div style="font-size: 11px; color: grey;">文字颜色:{{ attrs.fontColor }}</div>
</div>
</div>
<div class="attr-items flex items-center q-mt-lg">
<div class="color-box q-mr-lg">
<Popover trigger="mouseenter" class="full-height full-width">
<template #content>
<ColorPicker :modelValue="attrs.stroke" @update:modelValue="value => updateStrokeHandler(value)"/>
</template>
<div class="full-height full-width" style="border:1px solid #EEE;" :style="{backgroundColor:attrs.stroke}"></div>
</Popover>
</div>
<div style="font-size: 11px; color: grey;">边框颜色:{{ attrs.stroke }}</div>
</div>
<div class="attr-items flex items-center q-mt-md q-mb-md">
<div class="color-box q-mr-lg">
<Popover trigger="mouseenter" class="full-height full-width">
<template #content>
<ColorPicker :modelValue="attrs.fill" @update:modelValue="value => updateFillHandler(value)"/>
</template>
<div class="full-height full-width" style="border:1px solid #EEE;" :style="{backgroundColor:attrs.fill}"></div>
</Popover>
</div>
<div style="font-size: 11px; color: grey;">填充颜色:{{ attrs.fill }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor,Label} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
import MapService from '@/services/MapService';
const mapStore = useMapStore()
const { current,fillColor } = storeToRefs(mapStore)
const attrs = reactive({
arrow:-1,
textEnable:false,
fontSize:12,
fontAlign:0,
fontText:'',
fontColor:'#000',
selectObj:''
} as any)
const options = ref<{label:string,value:string,center:number[]}[]>([])
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}])
const updateFilter = (t: number, val: number) => {
switch (t) {
case 0:
attrs.strokeWidth = val
break;
case 1:
attrs.scale = val
setTimeout(() => {
calcLabelLocal(undefined,-1)
}, 500);
break;
default:
break;
}
updateLineStyle(current.value)
}
const updateFillHandler = (val:any) =>{
attrs.fill = val
attrs.fillOpacity = tinycolor(val).getAlpha()
updateLineStyle(current.value)
}
const updateStrokeHandler = (val:any) =>{
attrs.stroke = val
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)
chi.strokeWidth= attrs.strokeWidth
chi.strokeOpacity = attrs.strokeOpacity
chi.stroke = am4coreColor(tinycolor(attrs.stroke).toHexString())
chi.scale = attrs.scale
chi.fillOpacity = attrs.fillOpacity
chi.fill = am4coreColor(tinycolor(attrs.fill).toHexString())
tempChi.strokeWidth= attrs.strokeWidth
tempChi.strokeOpacity = attrs.strokeOpacity
tempChi.stroke = am4coreColor(tinycolor(attrs.stroke).toHexString())
tempChi.scale = attrs.scale
tempChi.fillOpacity = attrs.fillOpacity
tempChi.fill = am4coreColor(tinycolor(attrs.fill).toHexString())
}
const createLabel = ()=>{
let labelChildren = current.value!.mapImages.create();
labelChildren.latitude = current.value.data[0].latitude;
labelChildren.longitude = current.value.data[0].longitude;
let label = labelChildren.createChild(Label)
label.horizontalCenter = "middle";
// label.verticalCenter = "top";
label.fontSize = 13;
label.fontFamily = 'pingfangr'
label.fill = am4coreColor(fillColor.value.realColor)
label.text= '文字内容'
//label.fontWeight = 'bold'
attrs.fontSize = 13
attrs.fill = fillColor.value.realColor
attrs.text = '文字内容'
attrs.fontAlign = 0
labelChildren.children.getIndex(0).hide()
calcLabelLocal(label,0)
}
const calcLabelLocal = (label?:Label,align:number=0) =>{
if(!label){
label = getCurrentLabel()
align = align==-1? label!.marginBottom as number:align
}
if(label){
let scale = current.value.mapImages.template.children.getIndex(0).scale
let radius = current.value.mapImages.template.children.getIndex(0).radius
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
}
}
const searchPoiInfoHandler = async (val:string)=>{
if(val=='') return
searchLoading.value = true
let response = await MapService.queryPlaceInfoAsync(val)
options.value = response
searchLoading.value=false
}
const setLocationHandler = (value:string)=>{
if(value!='' && options.value && options.value.length>0){
let temp = options.value.find(x=>x.value==value)
if(temp){
current.value.data = [{
longitude:temp.center[0],
latitude:temp.center[1]
}]
}
}
}
const changeLabelStatus = (val:boolean)=>{
attrs.textEnable=val
if(val) {
createLabel()
} else {
current.value.mapImages.removeIndex(1)
}
}
const updateLabFontSizeHandler = (val:number) =>{
let lab = getCurrentLabel()
if(lab){
lab.fontSize=val
setTimeout(() => {
calcLabelLocal(undefined,-1)
}, 500);
}
}
const setLabelTextHandler = (val:string)=>{
let lab = getCurrentLabel()
if(lab){
lab.text=val
calcLabelLocal(lab,lab.marginBottom)
}
}
const setLabelColorHandler = (val:string)=>{
let lab = getCurrentLabel()
if(lab){
lab.fill = am4coreColor(tinycolor(val).toHex8String())
attrs.fontColor = tinycolor(val).toHex8String()
}
}
const getCurrentLabel = ()=>{
let c = current.value
if(c.mapImages.length>1 && c.mapImages.getIndex(1).children.length>1){
return c.mapImages.getIndex(1).children.getIndex(1)
}
return null
}
const setAttribute = ()=>{
let c = current.value
let chi = c.mapImages.template.children.getIndex(0)
attrs.strokeWidth=chi.strokeWidth
attrs.stroke=tinycolor(chi.stroke.hex).setAlpha(chi.strokeOpacity).toHex8String()
attrs.strokeOpacity=chi.strokeOpacity
attrs.scale=chi.scale
attrs.fill=tinycolor(chi.fill.hex).setAlpha(chi.fillOpacity).toHex8String()
attrs.fillOpacity=chi.fillOpacity
let lab = getCurrentLabel()
if(lab){
attrs.textEnable = true
attrs.fontText = lab.text
attrs.fontSize = lab.fontSize??15
attrs.fontAlign = lab.marginBottom
attrs.fontColor = tinycolor(lab.fill?.hex??'#000000').toHex8String()
//attrs.fontSize =
}else{
attrs.textEnable = false
}
}
const removeMark = ()=>{
mapStore.setRemoveMark(current.value)
}
setAttribute()
watch(()=>current.value,()=>{
setAttribute()
})
</script>
<template>
<div class="attr-box" @click.stop="(e:MouseEvent)=>e.stopPropagation()">
<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="removeText" />
</div>
</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="72" :min="11" :step="1" :value="attrs.fontSize"
@update:value="value => current.fontSize = value" />
</div>
<div class="attr-items grey-bg" style="border-top-width: 1px; font-size: 11px;padding: 10px;">
<el-input input-style="border: none !important; background: none !important;" @input="changeTextHandler" v-model="attrs.text" :rows="5" type="textarea" placeholder="文字..." />
</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.fill" @update:modelValue="value => updateFillHandler(value)"/>
</template>
<div class="full-height full-width" :style="{backgroundColor:attrs.fill}"></div>
</Popover>
</div>
<div>{{ attrs.fill }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import { reactive, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { color as am4coreColor} from "@amcharts/amcharts4/core";
import { watch } from 'vue';
const mapStore = useMapStore()
const { current,fillColor } = storeToRefs(mapStore)
const attrs = reactive({} as any)
const updateFillHandler = (val:any) =>{
attrs.fill = tinycolor(val).toHex8String()
current.value.fill = am4coreColor(attrs.fill)
}
const changeTextHandler = (val:string) => current.value.text =val
const setAttribute = ()=>{
attrs.text =current.value.text
attrs.fontSize = current.value.fontSize
attrs.fill = current.value.fill?tinycolor(current.value.fill.rgba).setAlpha(current.value.fill.alpha).toHex8String():fillColor.value.realColor
}
const removeText = ()=>{
mapStore.removeCurrentText()
}
setAttribute()
watch(()=>current.value,()=>{
setAttribute()
})
</script>
<template>
<LineAttributes v-if="current && current._className=='MapArc'"></LineAttributes>
<AreaAttributes v-if="current && current._className=='MapPolygon'"></AreaAttributes>
<MarkAttributes v-if="current && current._className=='MapImageSeries'"></MarkAttributes>
<TextAttributes v-if="current && current._className=='Label'"></TextAttributes>
<CountriesAttrbutes v-if="current && current=='Country'"></CountriesAttrbutes>
</template>
<script setup lang="ts">
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import LineAttributes from './LineAttributes.vue';
import AreaAttributes from './AreaAttributes.vue';
import MarkAttributes from './MarkAttributes.vue';
import TextAttributes from './TextAttributes.vue';
import CountriesAttrbutes from './CountriesAttrbutes.vue';
const mapStore = useMapStore()
const { current } = storeToRefs(mapStore)
</script>
<style>
@import url('../../../assets/styles/mapIcon.css');
.attr-box {
width: 208px;
background-color: #FFF !important;
background-image: none !important;
right: 20px;
left: unset;
top: 45px;
position: absolute;
z-index: 9;
}
.attr-box .attr-header {
height: 30px;
line-height: 30px;
font-size: 10px;
overflow: hidden;
padding: 0 5px 0 0;
background-color: #ebebeb;
border-color: rgba(204, 204, 204, .3);
margin-right: 0;
}
.grey-bg {
background-color: #ebebeb;
border-color: rgba(204, 204, 204, .3);
}
.attr-box .attr-header .drag-icon {
width: 6px;
/* height: 30px; */
margin: 0 8px 0 5px;
}
.attr-box .attr-header .title {
font-size: 11px;
color: rgba(0, 0, 0, .4);
}
.attr-box .attr-header .btn {
cursor: pointer;
}
.attr-box .attr-header .btn:hover {
color: #f56c6c;
}
.attr-box .attr-items {
padding: 5px 15px;
}
.attr-box .attr-items i {
color: #000;
font-size: 20px;
/* line-height: 40px; */
}
.attr-box .attr-items .arrow-type{
border-right: 1px solid rgba(0,0,0,.05);
border-top: 1px solid rgba(0,0,0,.05);
border-bottom: 1px solid rgba(0,0,0,.05);
}
.attr-box .attr-items .arrow-type>i {
color: rgba(0,0,0,.2);
width: 100%;
height: 50px;
font-size: 20px;
line-height: 50px;
border-style: none solid none none;
}
.attr-box .attr-items .arrow-type:hover,
.attr-box .attr-items .arrow-type.active{
background: #FFF !important;
cursor: pointer;
}
.attr-box .attr-items .arrow-type:hover i,
.attr-box .attr-items .arrow-type.active i{
color: #000;
}
.attr-box .color-box{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==');
background-repeat: repeat;
background-size: 10px;
background-position: top left;
width: 34px;
height: 34px;
}
.attr-box .el-checkbox__label{
font-size: 11px;
padding-right: 16px;
}
</style>
\ No newline at end of file
<template>
<div class="tools-box rounded" ref="toolsRef" draggable="true" :style="{top:location.top+'px',left:location.left+'px'}" @dragstart="dragstartHandler" @dragend="dragendHandler">
<div class="grip"></div>
<div class="tools-item mouse" @click.stop="setOperaHandler(0)" :class="{'active':operaIndex==0,'disable-item':!canOpera}"></div>
<div class="tools-item more" @click.stop="setOperaHandler(1)" :class="{'active':operaIndex==1,'disable-item':!canOpera}">
<Popover trigger="mouseenter" placement="right" class="full-height full-width">
<template #content>
<el-menu v-if="canOpera" :default-active="fillType" style="border: none !important;">
<el-menu-item :index="1" @click="fillType=1" style="height: 36px;">
<span>区域填充</span>
</el-menu-item>
<el-menu-item :index="2" @click="fillType=2" style="height: 36px;">
<span>全部填充</span>
</el-menu-item>
</el-menu>
</template>
<div class="clash child-item" :style="{backgroundColor:fillColor.realColor}"></div>
</Popover>
</div>
<div class="tools-item line" @click.stop="setOperaHandler(2)" :class="{'active':operaIndex==2,'disable-item':!canOpera}"></div>
<div class="tools-item more" @click.stop="setOperaHandler(5)" :class="{'active':operaIndex==5,'disable-item':!canOpera}">
<Popover trigger="mouseenter" placement="right" class="full-height full-width">
<template #content>
<el-menu v-if="canOpera" :default-active="markSelect" style="border: none !important;">
<el-menu-item :index="1" @click="marker=markerPath" style="height: 36px;">
<svg viewBox="0 0 32 32" width="20" height="20" preserveAspectRatio="none"><path :d="markerPath"></path></svg>
</el-menu-item>
<el-menu-item :index="2" @click="marker='circle'" style="height: 36px;">
<div class="temp-circle"></div>
</el-menu-item>
</el-menu>
</template>
<svg viewBox="0 0 32 32" width="20" height="20" preserveAspectRatio="none" v-if="marker!='circle'"><path :d="markerPath"></path></svg>
<div class="temp-circle" v-else></div>
</Popover>
</div>
<div class="tools-item" @click.stop="setOperaHandler(3)" :class="{'active':operaIndex==3,'disable-item':!canOpera}">
<span class="alifont">T</span>
</div>
<div class="tools-item" :class="{'active':operaIndex==4,'disable-item':!canOpera}">
<div class="menucolor child-item">
<Popover trigger="mouseenter" class="full-height full-width">
<template #content>
<ColorPicker v-if="canOpera" :modelValue="fillColor.realColor" @update:modelValue="value => updateFillHandler(value)"/>
</template>
<div class="full-height" :style="{backgroundColor:fillColor.realColor}"></div>
</Popover>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Popover from '@/components/Popover.vue'
import tinycolor from 'tinycolor2'
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
const location = ref<{
top:number,
left:number
}>({
top:30,
left:30
})
const move = ref<{
startX:number,
startY:number
}>({
startX:0,
startY:0
})
const toolsRef = ref()
const mapStore = useMapStore()
const { operaIndex,fillColor,fillType,canOpera,marker } = storeToRefs(mapStore)
const markSelect = marker?.value=='circle'?2:1
const markerPath = ref("M3.5,13.277C3.5,6.22,9.22,0.5,16.276,0.5C23.333,0.5,29.053,6.22,29.053,13.277C29.053,14.54,28.867,15.759,28.526,16.914C26.707,24.271,16.219,32.5,16.219,32.5C16.219,32.5,4.37,23.209,3.673,15.542C3.673,15.542,3.704,15.536,3.704,15.536C3.572,14.804,3.5,14.049,3.5,13.277C3.5,13.277,3.5,13.277,3.5,13.277M16.102,16.123C18.989,16.123,21.329,13.782,21.329,10.895C21.329,8.008,18.989,5.668,16.102,5.668C13.216,5.668,10.876,8.008,10.876,10.895C10.876,13.782,13.216,16.123,16.102,16.123C16.102,16.123,16.102,16.123,16.102,16.123")
marker.value = markerPath.value
const dragstartHandler =(e:DragEvent)=>{
move.value = {
startX:e.clientX,
startY:e.clientY
}
}
const dragendHandler =(e:DragEvent)=>{
let maxX=(document.querySelector('.map-content')?.clientWidth??0)-toolsRef.value.clientWidth
let maxY=(document.querySelector('.map-content')?.clientHeight??0)-toolsRef.value.clientHeight
let x = location.value.left + (e.clientX - move.value.startX);
let y = location.value.top + (e.clientY - move.value.startY);
location.value.left = x<0?0:(x>maxX?maxX:x);
location.value.top = y<0?0:(y>maxY?maxY:y);
}
const setOperaHandler = (i:number) => {
if(!canOpera.value) i=0
operaIndex.value = i
}
const updateFillHandler = (val:any) =>{
fillColor.value.color = parseInt(tinycolor(val).toHex(),16)
fillColor.value.opacity = tinycolor(val).getAlpha()
fillColor.value.realColor = tinycolor(val).toHex8String()
}
</script>
<style scoped>
.tools-box{
position: absolute;
z-index: 9;
background: rgba(255, 255, 255, .8);
width:55px;
}
.grip{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703830679000_621.webp');
background-repeat: no-repeat;
height: 5px;
background-position: center center;
margin: 0px auto;
padding: 5px 0px;
cursor: move;
}
.tools-item{
border-top:1px solid rgba(204, 204, 204, 0.3);
height: 53px;
text-align: center;
line-height: 53px;
background-color: rgb(235, 235, 235);
background-position:center;
background-repeat:no-repeat;
background-size: 32px;
font-size: 32px;
color: #000;
cursor: pointer !important;
}
.more{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703835556000_358.png');
background-repeat: no-repeat;
background-position: right bottom;
background-size: 10px;
}
.mouse {
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703835556000_666.png');
}
.clash{
background-position:center;
background-repeat:no-repeat;
background-size: 32px;
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703835556000_459.png');
}
.tools-item:hover .clash,.tools-item.active .clash{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703835556000_578.png');
}
.line{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703835556000_618.png');
}
.menucolor{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==');
background-repeat: repeat;
background-size: 10px;
background-position: top left;
}
.tools-item .child-item{
margin-top: calc((100% - 32px) / 2);
margin-left: auto;
margin-right: auto;
width: 32px;
height: 32px;
}
.tools-item:hover,.tools-item.active{
background-color: #FFF;
}
.tools-item.active{
cursor: default !important;
}
.disable-item{
cursor:not-allowed;
}
.temp-circle{
width: 18px;
height: 18px;
border-radius: 100%;
border:2px solid #FFF;
background-color: #000;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
display: inline-block;
}
</style>
\ No newline at end of file
<template>
<el-dialog class="map-box-bg" v-loading="dataLoadingStatus" top="2.5vh" v-model="visibleStatus" :show-close="false" :close-on-press-escape="false" :close-on-click-modal="false" style="margin-bottom: 0; max-width:calc(100vw - 40px);width:calc(100vw - 40px);">
<template #header>
<div class="row items-center">
<div class="text-title col">地图创作</div>
<el-button link type="primary" size="mini" v-if="ConfigId" @click="loadTripMap"><el-icon style="margin-right: 5px;font-size: 16px;"><Loading /></el-icon>行程渲染</el-button>
<el-button link size="mini" @click="exportMapImage"><el-icon style="margin-right: 5px;font-size: 16px;"><Download /></el-icon>生成</el-button>
<el-button link size="mini" @click="closed"><el-icon style="margin-right: 5px;font-size: 16px;"><Close /></el-icon>退出</el-button>
</div>
</template>
<div class="map-content" v-loading="loadingStatus" :class="{'clash-mouse':operaIndex==1,'line-mouse':operaIndex==2,'text-mouse':operaIndex==3,'mark-mouse':operaIndex==5 && marker!='circle','circle-mouse':operaIndex==5 && marker=='circle'}" @click="createDrawPanelHandler">
<div ref="mapRef" class="full-height full-width"></div>
<tools></tools>
<map-line v-if="operaIndex==2 && createLineStatus && canOpera" :parent="mapRef" :position="mouse" @success="createLineStatus=false"></map-line>
<map-attributes></map-attributes>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import tools from './Tools.vue'
import MapAttributes from './MapAttributes/index.vue';
import mapLine from './Line.vue'
import { onMounted, ref, watch } from 'vue';
import useMap from './useMap'
import { useMapStore,useScreenStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import LineService from '@/services/LineService';
import { Loading,Download,Close } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus';
const props = defineProps({
visible:{
type:Boolean,
required:true,
default:true
}
})
const emit = defineEmits<{
(event: 'update:visible', payload: boolean): void,
(event: 'success', payload: string): void
(event: 'closed'): void
}>()
const visibleStatus = ref(props.visible)
const mapRef = ref<any>(null)
const mouse = ref<{x:number,y:number}>({
x:0,
y:0
})
const mapStore = useMapStore()
const createLineStatus = ref(false)
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 tripInfo = ref<any>()
watch(() => props.visible, () => {
visibleStatus.value = props.visible
})
watch(() => operaIndex.value, () => {
if(operaIndex.value!=2){
createLineStatus.value=false
}
})
const createDrawPanelHandler = (e:MouseEvent) => {
if(operaIndex.value==2){
mouse.value.x=e.offsetX
mouse.value.y=e.offsetY
createLineStatus.value=true
}
}
const loadTripMap = async () =>{
disposeAll()
initMap()
dataLoadingStatus.value=true
let response = await LineService.GetTripInfoAsync(ConfigId.value)
try {
tripInfo.value = response.data.data
if(tripInfo.value.TripCountryList && tripInfo.value.TripCountryList.length>0){
let countries = tripInfo.value.TripCountryList.map((x:any)=>x.MName)
setCountry(countries)
}
if(tripInfo.value.TripCityList && tripInfo.value.TripCityList.length>0){
let points:{latitude:number,longitude:number}[][] = []
let cities = tripInfo.value.TripCityList.map((x:any,i:number)=> {
let city = {
name:x.MName,
lat:Number(x.latitude?x.latitude:x.Lat),
lng:Number(x.longitude?x.longitude:x.Lng)
}
if(i>0){
let t = tripInfo.value.TripCityList[i-1]
//let p = [Number(t.latitude?t.latitude:t.Lat),Number(t.longitude?t.longitude:t.Lng),city.lat,city.lng]
let p = [
{
latitude:Number(t.latitude?t.latitude:t.Lat),
longitude:Number(t.longitude?t.longitude:t.Lng)
},
{
latitude:city.lat,
longitude:city.lng
}
]
points.push(p)
}
return city
})
setMarks(cities)
setLines(points)
}
//setCountry(['日本'])
} catch (error) {
}
dataLoadingStatus.value=false
}
const exportMapImage = () => {
ElMessageBox.confirm('地图导出后无法继续编辑,是否确认导出','提示',{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
let png = await exportPng()
if(png!='') emit('success',png)
else {
ElMessage.error({
message: '地图导出失败,请重试',
})
}
})
.catch((error) => { })
}
const closed = ()=>{
emit('closed')
}
onMounted(()=>{
setTimeout(() => {
initMap()
ConfigId.value = 9765
}, 2000);
})
</script>
<style>
.map-box-bg .el-dialog__body{
background-color: #fff;
background-image: linear-gradient(45deg,#ddd 25%,transparent 0),linear-gradient(45deg,transparent 75%,#ddd 0),linear-gradient(45deg,#ddd 25%,transparent 0),linear-gradient(45deg,transparent 75%,#ddd 0);
background-position: 0 0,6px 6px,6px 6px,12px 12px;
background-size: 12px 12px;
padding: 0 !important;
}
.map-content{
width: 100%;
height: 90vh;
position: relative;
overflow: hidden;
}
.clash-mouse{
cursor: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1703841822000_160.png'), auto !important;
}
.line-mouse{
cursor: crosshair !important;
}
.mark-mouse{
cursor: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1704867897000_892.png'), auto !important;
}
.circle-mouse{
cursor: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1704867897000_140.png'), auto !important;
}
.text-mouse{
cursor: text !important;
}
</style>
\ No newline at end of file
import { Ref, ref, watch } from 'vue';
import * as am4core from "@amcharts/amcharts4/core";
import * as am4maps from "@amcharts/amcharts4/maps";
import am4themes_animated from "@amcharts/amcharts4/themes/animated";
import am4geodata_worldHigh from "@amcharts/amcharts4-geodata/worldUltra";
import { useMapStore } from '@/store/index'
import { storeToRefs } from 'pinia';
import am4geodata_data_countries from "@amcharts/amcharts4-geodata/data/countries";
import am5geodata_lang_cn_ZH from "@amcharts/amcharts4-geodata/lang/cn_ZH";
export default (MapDOM:Ref<HTMLElement|undefined>,loadingStatus:Ref<boolean>)=>{
const mapStore = useMapStore()
const { operaIndex,fillColor,fillType,canOpera,newLine,lineSeries,removeMark,labelSeries,execut } = 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 countryPolygon: am4maps.MapPolygon
let back:am4core.ZoomOutButton
let addButton:am4core.Button
const CountriesKeys=ref<string[]>([])
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)
eventName.value = ''
})
map.zoomControl = new am4maps.ZoomControl();
map.zoomControl.slider.height = 100;
map.maxZoomLevel = 64
worldSeries = map.series.push(new am4maps.MapPolygonSeries());
worldSeries.exclude = ["AQ"];
if(CountriesKeys.value && CountriesKeys.value.length>0){
worldSeries.include = CountriesKeys.value
}
worldSeries.useGeodata = true;
worldSeries.clickable=true
let polygonTemplate = worldSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}";
polygonTemplate.tooltipColorSource
polygonTemplate.fill = am4core.color(`#818181`);
polygonTemplate.stroke = am4core.color(`#ffffff`);
polygonTemplate.strokeWidth = 0.1
polygonTemplate.events.on("hit",(e)=>{
createCountry(e)
})
worldSeries.events.on('ready',(e)=>{
console.log('ready.........')
loadingStatus.value=false
})
initCountry()
createBackButton()
createAddButton()
initLineSeries()
initLabelSeries()
}
const initCountry =()=>{
countrySeries = map.series.push(new am4maps.MapPolygonSeries());
countrySeries.useGeodata = true;
countrySeries.hide();
countrySeries.geodataSource.events.on("done", () => {
worldSeries.hide();
countrySeries.show();
});
countryPolygon = countrySeries.mapPolygons.template;
countryPolygon.tooltipText = "{name}";
countryPolygon.nonScalingStroke = true;
countryPolygon.fill = am4core.color(`#818181`);
countryPolygon.stroke = am4core.color(`#ffffff`);
countryPolygon.strokeWidth = 0.1
countryPolygon.strokeDasharray='20,20'
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)
else {
eventName.value='MapPolygon'
mapStore.setCurrent(e.target)
}
})
}
const disposeAll = () =>{
map.disposeChildren()
map.dispose()
}
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)=>{
setTimeout(() => {
map.exporting.getImage("png").then(r=>{
map.zoomControl.show()
if(tempBack) back.show()
if(tempAdd) addButton.show()
loadingStatus.value=false
resolve(r)
}).catch((e)=>{
map.zoomControl.show()
if(tempBack) back.show()
if(tempAdd) addButton.show()
loadingStatus.value=false
resolve('')
})
}, 1000)
})
}
const createMarkImageHandler = (e:any)=>{
let markerPath = mapStore.getMarker
let imageSeries = map.series.push(new am4maps.MapImageSeries())
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.fillOpacity = fillColor.value.opacity
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.scale = 1;
}else{
var marker1 = imageSeries.mapImages.template.createChild(am4core.Circle);
marker1.path = markerPath
marker1.fill = am4core.color(fillColor.value.realColor.substring(0,7));
marker1.fillOpacity = fillColor.value.opacity
marker1.stroke = am4core.color("#FFFFFF");
marker1.strokeWidth = 0;
marker1.scale = 0.8;
marker1.horizontalCenter = "middle";
marker1.verticalCenter = "middle";
}
imageSeries.mapImages.template.nonScaling = true;
imageSeries.mapImages.template.propertyFields.latitude = "latitude";
imageSeries.mapImages.template.propertyFields.longitude = "longitude";
imageSeries.data = [
map.svgPointToGeo({x:e.svgPoint.x,y:e.svgPoint.y})
]
imageSeries.events.on("dragstart",()=>{
map.panBehavior = 'none'
})
imageSeries.events.on("dragstop",()=>{
map.panBehavior = "move"
})
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}[])=>{
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.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",()=>{
map.panBehavior = 'none'
})
imageSeries.events.on("dragstop",()=>{
map.panBehavior = "move"
})
imageSeries.events.on("hit",(e)=>{
eventName.value = 'MapImageSeries'
mapStore.setCurrent(e.target)
})
setTimeout(() => {
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
// labelChildren.children.getIndex(0).hide()
// label.marginBotton=0
label.y = 6
label.paddingLeft = 6
label.x = -2.5
setTimeout(() => {
try {
// @ts-ignore
labelChildren!.children!.getIndex(0).hide()
} catch (error) { }
}, 1000);
}, 1000);
}
}
const setLines = (vals:{latitude:number,longitude:number}[][])=>{
for (let i = 0; i < vals.length; i++) {
const item = vals[i];
let points:am4core.IGeoPoint[] = item
createLine(points)
}
}
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{
var dataItem = e.target.dataItem as any;
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) => {
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)
ev.target.isHover = false;
// @ts-ignore
countrySeries.geodata = null
// @ts-ignore
countrySeries.include = null
countrySeries.invalidateData()
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
})
back.show();
addButton.show()
canOpera.value=true
}
}
const setCountry = (names:string[]) => {
mapStore.clearCheckedCountries()
let allCountries = mapStore.getAllCountries
let countriesData:any[] = []
Object.keys(allCountries!).forEach((key:string)=>{
if(names.includes(allCountries![key])){
countriesData.push({
id:key,
name:allCountries![key]
});
}
})
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)
// @ts-ignore
countrySeries.geodata = null
// @ts-ignore
countrySeries.include = null
countrySeries.invalidateData()
setTimeout(() => {
try {
map.zoomToMapObject(worldSeries.getPolygonById(first.id))
} 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
})
} 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)
geoUrl = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
mapStore.setCheckedCountries({
id:item.id,
name:item.name,
url:geoUrl
})
idArray.push(item.id)
}
countrySeries.geodata = am4geodata_worldHigh
countrySeries.include=idArray
countrySeries.invalidateData()
worldSeries.hide();
countrySeries.show();
setTimeout(() => {
try {
map.zoomToMapObject(countrySeries.getPolygonById(idArray![0]))
} catch (error) {
}
}, 500);
}
back.show();
addButton.show()
canOpera.value=true
}
}
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)
geoUrl = "https://cdn.amcharts.com/lib/4/geodata/json/" + geoUrl + ".json";
mapStore.setCheckedCountries({
id:id,
name:payload.name,
url:geoUrl
})
idArray.push(id)
}else{
let ri = idArray.indexOf(id)
if(ri!=-1) {
idArray.splice(ri,1)
tempChecked.splice(ri,1)
}
}
if (idArray.length>1) {
countrySeries.geodata = am4geodata_worldHigh
countrySeries.include=idArray
countrySeries.invalidateData()
setTimeout(() => {
map.zoomToMapObject(countrySeries.getPolygonById(id))
}, 500);
} else if(idArray.length==1){
// @ts-ignore
countrySeries.geodata = null
// @ts-ignore
countrySeries.include = null
countrySeries.invalidateData()
countrySeries.geodataSource.url = tempChecked[0].url
countrySeries.geodataSource.load();
} else{
worldSeries.show();
map.goHome();
countrySeries.hide();
back.hide();
addButton.hide()
canOpera.value = false
operaIndex.value = 0
mapStore.clearCheckedCountries()
mapStore.setCurrent(null)
}
}
const createBackButton = () =>{
back = map.createChild(am4core.ZoomOutButton);
back.align = "right";
back.positionInvalid
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() {
worldSeries.show();
map.goHome();
countrySeries.hide();
back.hide();
addButton.hide()
canOpera.value = false
operaIndex.value = 0
mapStore.clearCheckedCountries()
mapStore.setCurrent(null)
});
}
const createAddButton = ()=>{
addButton = map.createChild(am4core.ZoomOutButton)
addButton.positionInvalid
addButton.margin(0,20,0,0)
addButton.align="right"
addButton.y = 40
addButton.icon = new am4core.Sprite()
addButton.icon.fill = am4core.color(`#ffffff`)
addButton.icon.scale = 0.018
addButton.icon.path = "M678.272 98.176C656 96 629.12 96 596.224 96H427.776c-32.896 0-59.84 0-82.048 2.176-23.232 2.304-43.712 7.168-63.104 18.304-19.392 11.136-33.92 26.432-47.488 45.376-13.056 18.048-26.56 41.344-43.008 69.76L108.48 375.808c-16.512 28.48-30.08 51.84-39.36 72.192-9.728 21.312-15.808 41.6-15.808 64s6.08 42.688 15.808 64c9.216 20.352 22.848 43.712 39.36 72.192l83.648 144.192c16.448 28.416 29.952 51.712 43.008 69.76 13.632 18.944 28.096 34.24 47.488 45.44 19.392 11.136 39.872 16 63.104 18.24 22.208 2.176 49.152 2.176 82.048 2.176h168.448c32.896 0 59.84 0 82.048-2.176 23.232-2.304 43.712-7.104 63.104-18.304 19.392-11.136 33.92-26.432 47.488-45.376 13.056-18.048 26.56-41.344 43.008-69.76l83.648-144.192c16.512-28.48 30.08-51.84 39.36-72.192 9.728-21.312 15.808-41.6 15.808-64s-6.08-42.688-15.808-64c-9.28-20.352-22.848-43.712-39.36-72.192l-83.648-144.192c-16.448-28.416-29.952-51.712-43.008-69.76-13.632-18.944-28.096-34.24-47.488-45.44-19.392-11.072-39.872-16-63.104-18.24zM512 661.376a149.312 149.312 0 1 1 0-298.688 149.312 149.312 0 0 1 0 298.624z"
addButton.readerTitle = '设置国家图层'
addButton.cursorOverStyle = am4core.MouseCursorStyle.pointer
addButton.hide();
addButton.events.on("hit", function() {
eventName.value='Country'
mapStore.setCurrent('Country')
});
}
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})
let labelChildren = labelSeries.value!.mapImages.create();
labelChildren.latitude = point.latitude;
labelChildren.longitude = point.longitude;
let label = labelChildren.createChild(am4core.Label)
label.horizontalCenter = "middle";
label.verticalCenter = "middle";
label.fontSize = 15;
label.fontFamily = 'pingfangr'
label.nonScaling = true;
label.interactionsEnabled = false;
label.fill = am4core.color(fillColor.value.realColor)
label.text= '文字...'
label.cursorOverStyle = am4core.MouseCursorStyle.pointer
label.draggable = true
label.interactionsEnabled = true
label.events.on("dragstart",()=>{
map.panBehavior = 'none'
})
label.events.on("dragstop",()=>{
map.panBehavior = "move"
})
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 = ()=>{
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')}`);
lineSeries.value!.mapLines.template.strokeOpacity = fillColor.value.opacity
lineSeries.value!.mapLines.template.nonScalingStroke = true;
lineSeries.value!.mapLines.template.line.controlPointDistance = 0;
lineSeries.value!.mapLines.template.line.controlPointPosition = 0.5;
lineSeries.value!.mapLines.template.line.cursorOverStyle = am4core.MouseCursorStyle.pointer
lineSeries.value!.mapLines.template.strokeLinecap = 'round'
}
const createLine = (points:am4core.IGeoPoint[]) => {
let line = lineSeries.value!.mapLines.create();
line.multiGeoLine = [points];
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"
})
//var tria = bullet.createChild();
mapStore.setCurrent(line)
}
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)=>{
if (newVal) {
setMulitpCountries(newVal.payload,newVal.exec)
}
})
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
break
}
}
if(i!=-1){
map.series.removeIndex(i)
mapStore.setCurrent(null)
removeMark.value=null
}
}
})
return {
initMap,
setCountry,
setMarks,
setLines,
disposeAll,
exportPng
}
}
\ No newline at end of file
// import * as am5 from "@amcharts/amcharts5";
// import * as am5map from "@amcharts/amcharts5/map";
// import am5geodata_worldLow from "@amcharts/amcharts5-geodata/worldLow";
// import { loadMap } from "@amcharts/amcharts5-geodata";
// import am5geodata_lang_cn_ZH from "@amcharts/amcharts5-geodata/lang/cn_ZH";
// import am5geodata_data_countries from "@amcharts/amcharts5-geodata/data/countries";
// import { ElMessage } from 'element-plus'
// import am5themes_Animated from '@amcharts/amcharts5/themes/Animated';
// import { useMapStore } from '@/store/index'
// import { storeToRefs } from 'pinia';
// import { Ref, reactive, ref } from "vue";
// import tinycolor from "tinycolor2";
// export default (dom: Ref<HTMLElement|undefined>) => {
// let root:am5.Root;
// let chart:am5map.MapChart;
// let zoomControl:am5map.ZoomControl;
// let homeButton:am5.Button;
// let worldSeries:am5map.MapPolygonSeries;
// let countrySeries:am5map.MapPolygonSeries;
// const mapStore = useMapStore()
// const { operaIndex,fillColor,fillType,regions } = storeToRefs(mapStore)
// const pixelMapper = reactive({
// lineColor:'',
// movingObject:undefined as any,
// drawingLine:false,
// lineX1:0,
// lineY1:0,
// lineX2:0,
// lineY2:0,
// tempLine:null as any
// })
// const handleColor = ()=>{
// let c = tinycolor(fillColor.value.color.toString(16).padStart(6, '0'));
// let rgb = c.toRgb();
// let color = `rgba(${rgb.r},${rgb.g},${rgb.b},${fillColor.value.opacity})`;
// return color;
// }
// const setMapPain = (canDrag:boolean)=>{
// if(chart){
// let valX:"translateX" | "none" | "rotateX" | undefined = !canDrag?'translateX':'none'
// let valY:"translateY" | "none" | "rotateY" | undefined = !canDrag?'translateY':'none'
// chart.set('panX',valX);
// chart.set('panY',valY);
// }
// }
// const update = (e:MouseEvent) => {
// if (pixelMapper.drawingLine) {
// let zoomLevel = chart.get('zoomLevel')??1;
// if (pixelMapper.tempLine) {
// pixelMapper.tempLine.remove();
// }
// pixelMapper.lineX2 = e.offsetX;
// pixelMapper.lineY2 = e.offsetY;
// pixelMapper.tempLine = chart.series.push(
// am5.Line.new(root, {
// stroke: 5,
// fill: am5.color(fillColor.value.color),
// opacity:fillColor.value.opacity,
// data:[{
// "x": pixelMapper.lineX1 / zoomLevel,
// "y": pixelMapper.lineY1 / zoomLevel
// }, {
// "x": pixelMapper.lineX2 / zoomLevel,
// "y": pixelMapper.lineY2 / zoomLevel
// }]
// })
// )
// //am5.line(pixelMapper.map.container, [(pixelMapper.lineX1 - pixelMapper.map.mapContainer.x) / zoomLevel, (pixelMapper.lineX2 - pixelMapper.map.mapContainer.x) / zoomLevel], [(pixelMapper.lineY1 - pixelMapper.map.mapContainer.y) / zoomLevel, (pixelMapper.lineY2 - pixelMapper.map.mapContainer.y) / zoomLevel], pixelMapper.lineColor, 1, 1 / pixelMapper.map.zoomLevel());
// //pixelMapper.map.mapContainer.push(pixelMapper.tempLine);
// }
// // "@amcharts/amcharts5": "^5.7.2",
// // "@amcharts/amcharts5-fonts": "^5.0.1",
// // "@amcharts/amcharts5-geodata": "^5.1.2",
// // if (pixelMapper.movingObject) {
// // if (Math.abs(pixelMapper.map.mouseY - pixelMapper.startDragY) > 5 || Math.abs(pixelMapper.map.mouseX - pixelMapper.startDragX) > 5) {
// // if (pixelMapper.movingObject.locked) {
// // var latlng = pixelMapper.map.stageXYToCoordinates(pixelMapper.map.mouseX, pixelMapper.map.mouseY);
// // pixelMapper.movingObject.latitude = latlng.latitude;
// // pixelMapper.movingObject.longitude = latlng.longitude;
// // } else {
// // pixelMapper.movingObject.left = pixelMapper.map.mouseX;
// // pixelMapper.movingObject.top = pixelMapper.map.mouseY;
// // }
// // pixelMapper.movingObject.updatePosition();
// // }
// // }
// }
// const handleMapClick = (e:MouseEvent)=> {
// pixelMapper.lineColor = handleColor();
// pixelMapper.movingObject = undefined;
// setMapPain(true)
// let zoomLevel = chart.get('zoomLevel');
// if (operaIndex.value==2) {
// if (!pixelMapper.drawingLine) {
// pixelMapper.lineX1 = e.offsetX;
// pixelMapper.lineY1 = e.offsetY;
// pixelMapper.drawingLine = true;
// } else {
// pixelMapper.drawingLine = false;
// let latlng1 = chart.invert({x: pixelMapper.lineX1,y: pixelMapper.lineY1});
// let latlng2 = chart.invert({x: pixelMapper.lineX2,y: pixelMapper.lineY2});
// let geo:GeoJSON.LineString = {
// "type": "LineString",
// "coordinates": [
// [ latlng1.longitude, latlng1.latitude ],
// [ latlng2.longitude, latlng2.latitude ]
// ]
// }
// let mapLine = am5map.MapLineSeries.new(root,{
// geoJSON: geo,
// opacity:fillColor.value.opacity,
// stroke:am5.color(fillColor.value.color),
// });
// chart.series.push(mapLine)
// //mapLine.chart = pixelMapper.map;
// //mapLine.selectedColor = pixelMapper.lineColor;
// //mapLine.pixelMapperLine = true;
// //pixelMapper.map.dataProvider.lines.push(mapLine);
// // mapLine.validate();
// // pixelMapper.map.selectObject(mapLine);
// // pixelMapper.selectedObject = mapLine;
// // PMG.createPopup(mapLine);
// // pixelMapper.removeTempLine();
// }
// }
// }
// const initMap = () => {
// handleColor()
// if(dom.value){
// root = am5.Root.new(dom.value);
// root.setThemes([
// am5themes_Animated.new(root)
// ]);
// chart = root.container.children.push(
// am5map.MapChart.new(root, {
// projection: am5map.geoMercator()
// })
// );
// zoomControl = chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
// homeButton = zoomControl.children.moveValue(am5.Button.new(root, {
// visible:false,
// paddingTop: 10,
// paddingBottom: 10,
// icon:
// am5.Graphics.new(root, {
// svgPath: "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",
// fill: am5.color(0xffffff)
// })
// }), 0)
// homeButton.events.on("click", function() {
// chart!.goHome();
// worldSeries!.show();
// countrySeries!.hide();
// homeButton!.hide();
// mapStore.clearRegion()
// })
// worldSeries = chart.series.push(
// am5map.MapPolygonSeries.new(root, {
// geoJSON: am5geodata_worldLow,
// exclude: ["AQ"],
// geodataNames:am5geodata_lang_cn_ZH
// })
// );
// worldSeries.mapPolygons.template.setAll({
// tooltipText: "{name}",
// interactive: true
// });
// countrySeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
// visible: false,
// }));
// countrySeries.mapPolygons.template.setAll({
// // tooltipText: "{name}",
// // interactive: true,
// stroke: am5.color(0xffffff),
// strokeWidth: 0.1
// });
// worldSeries.mapPolygons.template.states.create("hover", {
// fill: am5.color(0x4bd8b5)
// });
// worldSeries.mapPolygons.template.events.on("click", (ev:any) => {
// if(operaIndex.value==0){
// var dataItem = ev.target.dataItem as any;
// let geoName = am5geodata_data_countries[dataItem.dataContext.id].find(x=>x.indexOf("High")!=-1)
// var zoomAnimation = worldSeries!.zoomToDataItem(dataItem);
// if(zoomAnimation){
// zoomAnimation.waitForStop().then((res: any) => {
// const geoJSON = loadMap(geoName??'').then(r=>{
// countrySeries!.setAll({
// geoJSON: r
// });
// countrySeries!.show();
// worldSeries!.hide(100);
// homeButton!.show();
// }).catch(e=>{
// ElMessage({
// message:`无法加载 ${dataItem.dataContext.name} 的地图`,
// type:'error'
// })
// })
// })
// }
// }
// });
// countrySeries.mapPolygons.template.events.on('click',(ev:any)=>{
// if(operaIndex.value!=1) return;
// if(fillType.value==2){
// if(regions.value && regions.value.length>0){
// regions.value.forEach(x=>{
// x.dataItem.dataContext.colorWasSet=false
// })
// }
// countrySeries!.mapPolygons.template.setAll({
// stroke: am5.color(0xffffff),
// strokeWidth: 0.1,
// fill: am5.Color.saturate(am5.color(fillColor.value.color), fillColor.value.opacity)
// });
// }else{
// var dataItem = ev.target.dataItem as any;
// console.log(countrySeries)
// console.log(ev.target)
// mapStore.setRegiogn(ev.target)
// dataItem.dataContext.colorWasSet=false
// ev.target.adapters.add("fill", (fill:any, target:any) => {
// var dataContext:any = target!.dataItem!.dataContext!;
// if (!dataContext.colorWasSet) {
// dataContext.colorWasSet = true;
// var color = am5.Color.saturate(am5.color(fillColor.value.color), 1);
// target.setRaw("fill", color);
// target.setRaw('fillOpacity', fillColor.value.opacity)
// return color;
// }
// else {
// return fill;
// }
// })
// }
// })
// let zoomLevel = chart.get('zoomLevel')??1;
// // chart.series.push(
// // am5.Line.new(root, {
// // stroke: am5.color(fillColor.value.color),
// // strokeWidth:5,
// // opacity:fillColor.value.opacity,
// // userData:[{
// // "x": 50 / zoomLevel,
// // "y": 50 / zoomLevel
// // }, {
// // "x": 500 / zoomLevel,
// // "y": 500 / zoomLevel
// // }]
// // })
// // )
// let latlng1 = chart.invert({x: 100,y: 100});
// let latlng2 = chart.invert({x: 100,y: 300});
// let latlng3 = chart.invert({x: 300,y: 300});
// let geo:GeoJSON.LineString = {
// "type": "LineString",
// "coordinates": [
// [ latlng1.longitude, latlng1.latitude ],
// [ latlng2.longitude, latlng2.latitude ],
// [ latlng3.longitude, latlng3.latitude ]
// ]
// }
// let routes:GeoJSON.GeoJSON = {
// "type": "FeatureCollection",
// "features": [{
// "type": "Feature",
// "properties": {
// },
// "geometry": geo
// }]
// };
// let mapLine = am5map.MapLineSeries.new(root,{
// geoJSON: routes,
// lineType: "curved",
// opacity:fillColor.value.opacity,
// stroke:am5.color(0x000000)//am5.color(fillColor.value.color)
// });
// mapLine.mapLines.template.setRaw("strokeWidth",5)
// mapLine.mapLines.template.setRaw("strokeDasharray",20)
// mapLine.mapLines.template.setRaw("strokeDashoffset",0)
// mapLine.mapLines.template.setRaw("lineJoin",'round')
// mapLine.mapLines.template.setRaw("crisp", true);
// chart.series.push(mapLine)
// }
// }
// return {
// initMap,
// handleColor
// }
// }
// // let root:am5.Root;
// // let chart:am5map.MapChart;
// // let zoomControl:am5map.ZoomControl;
// // let homeButton:am5.Button;
// // let worldSeries:am5map.MapPolygonSeries;
// // let countrySeries:am5map.MapPolygonSeries;
// // export const useMap = (dom:HTMLElement|undefined)=>{
// // const mapStore = useMapStore()
// // const { operaIndex,fillColor,fillType,regions } = storeToRefs(mapStore)
// // if(dom){
// // root = am5.Root.new(dom);
// // root.setThemes([
// // am5themes_Animated.new(root)
// // ]);
// // chart = root.container.children.push(
// // am5map.MapChart.new(root, {
// // projection: am5map.geoMercator()
// // })
// // );
// // chart.chartContainer.set("background", am5.Rectangle.new(root, {
// // fill: am5.color(0xd4f1f9),
// // fillOpacity: 0
// // }));
// // zoomControl = chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
// // homeButton = zoomControl.children.moveValue(am5.Button.new(root, {
// // visible:false,
// // paddingTop: 10,
// // paddingBottom: 10,
// // icon:
// // am5.Graphics.new(root, {
// // svgPath: "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",
// // fill: am5.color(0xffffff)
// // })
// // }), 0)
// // homeButton.events.on("click", function() {
// // chart.goHome();
// // worldSeries.show();
// // countrySeries.hide();
// // homeButton.hide();
// // mapStore.clearRegion()
// // })
// // worldSeries = chart.series.push(
// // am5map.MapPolygonSeries.new(root, {
// // geoJSON: am5geodata_worldLow,
// // exclude: ["AQ"],
// // geodataNames:am5geodata_lang_cn_ZH
// // })
// // );
// // worldSeries.mapPolygons.template.setAll({
// // tooltipText: "{name}",
// // interactive: true
// // });
// // countrySeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
// // visible: false,
// // }));
// // countrySeries.mapPolygons.template.setAll({
// // // tooltipText: "{name}",
// // // interactive: true,
// // stroke: am5.color(0xffffff),
// // strokeWidth: 0.1
// // });
// // worldSeries.mapPolygons.template.states.create("hover", {
// // fill: am5.color(0x4bd8b5)
// // });
// // worldSeries.mapPolygons.template.events.on("click", (ev) => {
// // if(operaIndex.value==0){
// // var dataItem = ev.target.dataItem as any;
// // let geoName = am5geodata_data_countries[dataItem.dataContext.id].find(x=>x.indexOf("High")!=-1)
// // var zoomAnimation = worldSeries.zoomToDataItem(dataItem);
// // if(zoomAnimation){
// // zoomAnimation.waitForStop().then((res: any) => {
// // const geoJSON = loadMap(geoName??'').then(r=>{
// // countrySeries.setAll({
// // geoJSON: r
// // });
// // countrySeries.show();
// // worldSeries.hide(100);
// // homeButton.show();
// // }).catch(e=>{
// // ElMessage({
// // message:`无法加载 ${dataItem.dataContext.name} 的地图`,
// // type:'error'
// // })
// // })
// // })
// // }
// // }
// // });
// // countrySeries.mapPolygons.template.events.on('click',(ev)=>{
// // if(operaIndex.value!=1) return;
// // if(fillType.value==2){
// // if(regions.value && regions.value.length>0){
// // regions.value.forEach(x=>{
// // x.dataItem.dataContext.colorWasSet=false
// // })
// // }
// // countrySeries.mapPolygons.template.setAll({
// // stroke: am5.color(0xffffff),
// // strokeWidth: 0.1,
// // fill: am5.Color.saturate(am5.color(fillColor.value.color), fillColor.value.opacity)
// // });
// // }else{
// // var dataItem = ev.target.dataItem as any;
// // console.log(countrySeries)
// // console.log(ev.target)
// // mapStore.setRegiogn(ev.target)
// // dataItem.dataContext.colorWasSet=false
// // ev.target.adapters.add("fill", (fill:any, target:any) => {
// // var dataContext:any = target!.dataItem!.dataContext!;
// // if (!dataContext.colorWasSet) {
// // dataContext.colorWasSet = true;
// // var color = am5.Color.saturate(am5.color(fillColor.value.color), 1);
// // target.setRaw("fill", color);
// // target.setRaw('fillOpacity', fillColor.value.opacity)
// // return color;
// // }
// // else {
// // return fill;
// // }
// // })
// // }
// // })
// // //#region 绘制线条
// // // let lineSeries = chart.series.push(
// // // am5map.MapLineSeries.new(root, {
// // // lineType: "straight"
// // // })
// // // );
// // // lineSeries.mapLines.template.setAll({
// // // stroke: am5.color(0xff0000),
// // // strokeWidth: 2,
// // // strokeOpacity: 0.5
// // // });
// // // lineSeries.data.setAll([{
// // // "geometry": {
// // // "type": "LineString",
// // // "coordinates": [
// // // [ -73.778137, 40.641312 ],
// // // [ -0.454296, 51.470020 ]
// // // ]
// // // }
// // // }]);
// // //#endregion
// // }
// // }
// // export const setMapPain = (canDrag:boolean)=>{
// // if(chart){
// // let valX:"translateX" | "none" | "rotateX" | undefined = !canDrag?'translateX':'none'
// // let valY:"translateY" | "none" | "rotateY" | undefined = !canDrag?'translateY':'none'
// // chart.set('panX',valX);
// // chart.set('panY',valY);
// // }
// // }
......@@ -16,6 +16,7 @@ import '@/assets/styles/font.scss'
import Icon from '@/plugins/icon'
import Directive from '@/plugins/directive'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import VueKonva from 'vue-konva';
const app = createApp(App)
......@@ -25,6 +26,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.use(Icon)
app.use(Directive)
app.use(VueKonva)
app.use(createPinia())
app.mount('#app')
......@@ -122,6 +122,7 @@ import {
CheckOne,
CloseOne,
Info,
Earth,
} from '@icon-park/vue-next'
export interface Icons {
......@@ -249,6 +250,7 @@ export const icons: Icons = {
IconCheckOne: CheckOne,
IconCloseOne: CloseOne,
IconInfo: Info,
IconEarth: Earth,
}
export default {
......
......@@ -16,5 +16,9 @@ class LineService{
static async GetLineListAsync():Promise<HttpResponse>{
return Api.Post("line_post_GetAllList",{})
}
static async GetTripInfoAsync(configId:number):Promise<HttpResponse>{
return Api.Post("travel_get_GetTravelConfigMakeInfo",{configId})
}
}
export default LineService;
\ No newline at end of file
class MapService {
static queryPlaceInfoAsync = async (keyword: string): Promise<{ label: string, value: string, center: number[] }[]> => {
let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${keyword}.json?types=place&access_token=pk.eyJ1IjoiYWxleDkwMTIiLCJhIjoiY2xtOGw4NHdkMGFndTNjcnFkeWZncGc2dyJ9.lVrAdPHE0Dg5zoWFidfj4Q`
const response = await fetch(url)
return new Promise(async (resolve) => {
if (response.ok) {
let data = await response.json()
if (data.features && data.features.length > 0) {
let result: { label: string, value: string, center: number[] }[] = []
data.features.forEach((x: any) => {
result.push({
label: x.matching_text?`${x.matching_text}(${x.text})`:x.text,
value: x.id,
center: x.center
})
})
resolve(result)
}
}
resolve([])
})
}
}
export default MapService
\ No newline at end of file
......@@ -5,6 +5,7 @@ import { useKeyboardStore } from './keyboard'
import { useScreenStore } from './screen'
import { useFontStore } from './font'
import { useSellTemplate } from './sellTemplate'
import { useMapStore } from './map'
export {
useMainStore,
......@@ -13,5 +14,6 @@ export {
useKeyboardStore,
useScreenStore,
useFontStore,
useSellTemplate
useSellTemplate,
useMapStore
}
\ No newline at end of file
import { defineStore } from "pinia";
import {MapArcSeries,MapImageSeries} from "@amcharts/amcharts4/maps";
import { Lang } from "@amcharts/amcharts4-geodata/.internal/Lang";
export interface MapFill{
color:number,
opacity:number,
realColor:string
}
export interface MapCountryInfo{
id:string,
name:string,
url:string
}
export interface MapCountryOpera{
payload:any,
exec:number
}
interface MapState{
current:any,
fillColor:MapFill,
operaIndex:number,
fillType:number,
canOpera:boolean,
newLine:number[],
lineSeries:MapArcSeries|undefined,
marker:string,
removeMark:any,
labelSeries:MapImageSeries|undefined,
checkedCountries:MapCountryInfo[],
allCountries:Lang|null,
execut:MapCountryOpera|null
}
export const useMapStore = defineStore('map',{
state:(): MapState => ({
current:null,
fillColor:{
color:0x4bd8b5,
opacity:1,
realColor:'#4bd8b5'
},
operaIndex:0,
fillType:1,
canOpera:false,
newLine:[],
lineSeries:undefined,
marker:'',
removeMark:null,
labelSeries:undefined,
checkedCountries:[],
allCountries:null,
execut:null
}),
getters:{
getCurrent(state){
return state.current
},
getFillColor(state){
return state.fillColor
},
getOperaIndex(state){
return state.operaIndex
},
getFillType(state){
return state.fillType
},
getMarker(state){
return state.marker
},
getLang(state){
return state.allCountries
},
getCheckedCountries(state){
return state.checkedCountries
},
getAllCountries(state){
return state.allCountries
}
},
actions:{
setCurrent(newVal:any){
this.current = newVal
},
setFill(colorInfo:MapFill|any|undefined){
this.fillColor=colorInfo
},
setOperaIndex(val:number){
this.operaIndex = val
},
setFillType(val:number){
this.fillType = val
},
setMarker(val:string){
this.marker = val
},
removeCurrentLine(){
if(this.lineSeries && this.current && this.current._className=='MapArc'){
this.lineSeries.mapLines.removeValue(this.current)
this.current = null
}
},
setRemoveMark(val:string) {
this.removeMark = val
},
removeCurrentText(){
if(this.labelSeries){
this.labelSeries.mapImages.removeValue(this.current.parent)
this.current = null
}
},
setAllCountries(val:Lang){
this.allCountries = val
},
setCheckedCountries(val:MapCountryInfo){
this.checkedCountries.push(val)
},
clearCheckedCountries(){
this.checkedCountries = []
},
setCountriesChange(val:MapCountryOpera|null){
this.execut = val
}
// removeCurrentMark(){
// if(this.map && this.current && this.current._className=='MapImageSeries'){
// this.map.series.removeValue(this.current)
// this.current = null
// }
// }
}
})
\ No newline at end of file
......@@ -26,8 +26,10 @@ export type SlideDataSource = Ref<{
TravelAatas?:TravelAatas,
[key: string]: any;
}>
export type OperaKey = Ref<number>
export const injectKeySlideScale: InjectionKey<SlideScale> = Symbol()
export const injectKeySlideId: InjectionKey<SlideId> = Symbol()
export const injectKeyRadioGroupValue: InjectionKey<RadioGroupValue> = Symbol()
export const injectKeyDataSource: InjectionKey<SlideDataSource> = Symbol()
export const injectKeyTemplate: InjectionKey<SlideDataSource> = Symbol()
\ No newline at end of file
export const injectKeyTemplate: InjectionKey<SlideDataSource> = Symbol()
export const injectKeyMapOperaIndex: InjectionKey<OperaKey> = Symbol()
\ No newline at end of file
......@@ -52,6 +52,7 @@
<IconInsertTable class="handler-item" v-tooltip="'插入表格'" />
</Popover>
<IconFormula class="handler-item" v-tooltip="'插入公式'" @click="latexEditorVisible = true" />
<IconEarth class="handler-item" v-tooltip="'编辑地图'" @click="mapVisibleStatus = true" />
<!-- <Popover trigger="click" v-model:value="mediaInputVisible">
<template #content>
<MediaInput
......@@ -90,7 +91,7 @@
@update="data => { createLatexElement(data); latexEditorVisible = false }"
/>
</Modal>
<Maps :visible="mapVisibleStatus" @closed="mapVisibleStatus=false" @success="createMapImageHandler" v-if="mapVisibleStatus"></Maps>
<!-- 上传图片 -->
<UploadPicture v-if="imgPoolVisible"></UploadPicture>
</div>
......@@ -119,6 +120,7 @@ import Divider from '@/components/Divider.vue'
import Popover from '@/components/Popover.vue'
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
import UploadPicture from './UploadPicture.vue'
import Maps from '@/components/Maps/index.vue'
const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel } = storeToRefs(mainStore)
......@@ -167,6 +169,7 @@ const tableGeneratorVisible = ref(false)
const mediaInputVisible = ref(false)
const latexEditorVisible = ref(false)
const textTypeSelectVisible = ref(false)
const mapVisibleStatus = ref(false)
const getImgVis = () =>{
imgPoolVisible.value = true
......@@ -208,6 +211,11 @@ const toggleSelectPanel = () => {
mainStore.setSelectPanelState(!showSelectPanel.value)
}
const createMapImageHandler = (src:string) => {
createImageElement(src)
mapVisibleStatus.value=false
}
// 打开搜索替换面板
const toggleSraechPanel = () => {
mainStore.setSearchPanelState(!showSearchPanel.value)
......
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