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
This diff is collapsed.
<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
This diff is collapsed.
This diff is collapsed.
......@@ -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