Merge branch 'dev' of gitee.com:dromara/go-view into dev

Signed-off-by: 潘潘 <97274247@qq.com>
This commit is contained in:
潘潘 2022-10-14 07:01:47 +00:00 committed by Gitee
commit 656532e43d
83 changed files with 2388 additions and 606 deletions

View File

@ -22,6 +22,7 @@
"echarts-liquidfill": "^3.1.0",
"echarts-stat": "^1.2.0",
"echarts-wordcloud": "^2.0.0",
"gsap": "^3.11.3",
"highlight.js": "^11.5.0",
"html2canvas": "^1.4.1",
"keymaster": "^1.6.2",
@ -29,6 +30,7 @@
"naive-ui": "2.33.4",
"pinia": "^2.0.13",
"screenfull": "^6.0.1",
"three": "^0.145.0",
"vue": "^3.2.31",
"vue-demi": "^0.13.1",
"vue-i18n": "9.1.9",
@ -41,6 +43,7 @@
"@commitlint/cli": "^17.0.2",
"@commitlint/config-conventional": "^17.0.2",
"@types/node": "^16.11.26",
"@types/three": "^0.144.0",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"@vicons/carbon": "^0.12.0",

View File

@ -8,6 +8,7 @@ specifiers:
'@types/keymaster': ^1.6.30
'@types/lodash': ^4.14.184
'@types/node': ^16.11.26
'@types/three': ^0.144.0
'@typescript-eslint/eslint-plugin': ^5.18.0
'@typescript-eslint/parser': ^5.18.0
'@vicons/carbon': ^0.12.0
@ -31,6 +32,7 @@ specifiers:
eslint-plugin-import: ^2.26.0
eslint-plugin-prettier: ^4.0.0
eslint-plugin-vue: ^8.5.0
gsap: ^3.11.3
highlight.js: ^11.5.0
html2canvas: ^1.4.1
husky: ^8.0.1
@ -45,6 +47,7 @@ specifiers:
sass: ^1.49.11
sass-loader: ^12.6.0
screenfull: ^6.0.1
three: ^0.145.0
typescript: 4.6.3
vite: 2.9.9
vite-plugin-compression: ^0.5.1
@ -73,6 +76,7 @@ dependencies:
echarts-liquidfill: 3.1.0_echarts@5.3.3
echarts-stat: 1.2.0
echarts-wordcloud: 2.0.0_echarts@5.3.3
gsap: 3.11.3
highlight.js: 11.5.1
html2canvas: 1.4.1
keymaster: 1.6.2
@ -80,6 +84,7 @@ dependencies:
naive-ui: 2.33.4_vue@3.2.37
pinia: 2.0.14_ub5l46u3nefphax5x2tezui4oq
screenfull: 6.0.1
three: 0.145.0
vue: 3.2.37
vue-demi: 0.13.1_vue@3.2.37
vue-i18n: 9.1.9_vue@3.2.37
@ -92,6 +97,7 @@ devDependencies:
'@commitlint/cli': 17.0.2
'@commitlint/config-conventional': 17.0.2
'@types/node': 16.11.40
'@types/three': 0.144.0
'@typescript-eslint/eslint-plugin': 5.28.0_evi7yu7wunhzwb24olrfvzynny
'@typescript-eslint/parser': 5.28.0_sfmgizikprcxt7r54j7cnzjamu
'@vicons/carbon': 0.12.0
@ -905,7 +911,7 @@ packages:
dev: true
/@types/node/17.0.43:
resolution: {integrity: sha512-jnUpgw8fL9kP2iszfIDyBQtw5Mf4/XSqy0Loc1J9pI14ejL83XcCEvSf50Gs/4ET0I9VCCDoOfufQysj0S66xA==, registry: https://registry.npm.taobao.org/}
resolution: {integrity: sha512-jnUpgw8fL9kP2iszfIDyBQtw5Mf4/XSqy0Loc1J9pI14ejL83XcCEvSf50Gs/4ET0I9VCCDoOfufQysj0S66xA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/@types/node/-/node-17.0.43.tgz}
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@ -921,12 +927,22 @@ packages:
'@types/node': 17.0.43
dev: true
/@types/three/0.144.0:
resolution: {integrity: sha512-psvEs6q5rLN50jUYZ3D4pZMfxTbdt3A243blt0my7/NcL6chaCZpHe2csbCtx0SOD9fI/XnF3wnVUAYZGqCSYg==}
dependencies:
'@types/webxr': 0.5.0
dev: true
/@types/through/0.0.30:
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
dependencies:
'@types/node': 17.0.43
dev: true
/@types/webxr/0.5.0:
resolution: {integrity: sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA==}
dev: true
/@typescript-eslint/eslint-plugin/5.28.0_evi7yu7wunhzwb24olrfvzynny:
resolution: {integrity: sha512-DXVU6Cg29H2M6EybqSg2A+x8DgO9TCUBRp4QEXQHJceLS7ogVDP0g3Lkg/SZCqcvkAP/RruuQqK0gdlkgmhSUA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -1884,7 +1900,7 @@ packages:
dev: true
/csstype/2.6.20:
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/}
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/csstype/-/csstype-2.6.20.tgz}
dev: false
/csstype/3.0.11:
@ -2091,7 +2107,7 @@ packages:
dev: false
/echarts-wordcloud/2.0.0_echarts@5.3.3:
resolution: {integrity: sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g==, registry: https://registry.npm.taobao.org/}
resolution: {integrity: sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/echarts-wordcloud/-/echarts-wordcloud-2.0.0.tgz}
peerDependencies:
echarts: ^5.0.1
dependencies:
@ -3032,6 +3048,10 @@ packages:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
dev: true
/gsap/3.11.3:
resolution: {integrity: sha512-xc/iIJy+LWiMbRa4IdMtdnnKa/7PXEK6NNzV71gdOYUVeTZN7UWnLU0fB7Hi1iwiz4ZZoYkBZPPYGg+2+zzFHA==}
dev: false
/handlebars/4.7.7:
resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==}
engines: {node: '>=0.4.7'}
@ -4825,6 +4845,10 @@ packages:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
/three/0.145.0:
resolution: {integrity: sha512-EKoHQEtEJ4CB6b2BGMBgLZrfwLjXcSUfoI/MiIXUuRpeYsfK5aPWbYhdtIVWOH+x6X0TouldHKHBuc/LAiFzAw==}
dev: false
/through/2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true

View File

@ -17,6 +17,7 @@ export const scatterBasicUrl = '/mock/scatterBasic'
export const mapUrl = '/mock/map'
export const wordCloudUrl = '/mock/wordCloud'
export const treemapUrl = '/mock/treemap'
export const threeEarth01Url = '/mock/threeEarth01Data'
const mockObject: MockMethod[] = [
{
@ -91,6 +92,11 @@ const mockObject: MockMethod[] = [
method: RequestHttpEnum.GET,
response: () => test.fetchTreemap
},
{
url: threeEarth01Url,
method: RequestHttpEnum.GET,
response: () => test.threeEarth01Data
},
]
export default mockObject

View File

@ -254,4 +254,21 @@ export default {
msg: '请求成功',
data: tTreemapJson
},
// 三维地球
threeEarth01Data: {
code: 0,
status: 200,
msg: '请求成功',
data: [
{
startArray: { name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
endArray: [
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' },
{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }
]
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -1,5 +1,5 @@
<template>
<setting-item-box name="位置">
<setting-item-box v-if="targetData" name="位置">
<setting-item :name="`偏移 X${targetData.left || 0}px`">
<n-input-number v-model:value="targetData.left" size="small" step="10"></n-input-number>
</setting-item>

View File

@ -59,18 +59,18 @@ export const useChartDataFetch = (
const fetchFn = async () => {
const res = await customizeHttp(toRaw(targetComponent.request), toRaw(chartEditStore.requestGlobalConfig))
if (res && res.data) {
if (res) {
try {
const filter = targetComponent.filter
// eCharts 组件配合 vChart 库更新方式
if (chartFrame === ChartFrameEnum.ECHARTS) {
if (vChartRef.value) {
vChartRef.value.setOption({ dataset: newFunctionHandle(res.data, filter) })
vChartRef.value.setOption({ dataset: newFunctionHandle(res?.data, res, filter) })
}
}
// 更新回调函数
if (updateCallback) {
updateCallback(newFunctionHandle(res.data, filter))
updateCallback(newFunctionHandle(res?.data, res, filter))
}
} catch (error) {
console.error(error)
@ -90,7 +90,7 @@ export const useChartDataFetch = (
}
// eslint-disable-next-line no-empty
} catch (error) {
console.log(error);
console.log(error)
}
}

View File

@ -5,16 +5,20 @@ import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = ['legend', 'xAxis', 'yAxis']
export const seriesItem = {
type: 'bar',
barWidth: null,
barWidth: 15,
label:{
show: true,
position: 'top',
color: "#fff",
fontSize: 12,
},
itemStyle: {
color: null,
borderRadius: 0
borderRadius: 2
}
}
export const option = {
tooltip: {
show: true,

View File

@ -1,24 +1,13 @@
<template>
<!-- Echarts 全局设置 -->
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"></global-setting>
<CollapseItem
v-for="(item, index) in seriesList"
:key="index"
:name="`柱状图-${index + 1}`"
:expanded="true"
>
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
<SettingItemBox name="图形">
<SettingItem name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.itemStyle.color"
></n-color-picker>
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color"></n-color-picker>
</SettingItem>
<SettingItem>
<n-button size="small" @click="item.itemStyle.color = null">
恢复默认
</n-button>
<n-button size="small" @click="item.itemStyle.color = null"> 恢复默认 </n-button>
</SettingItem>
<SettingItem name="宽度">
<n-input-number
@ -30,24 +19,40 @@
></n-input-number>
</SettingItem>
<SettingItem name="圆角">
<n-input-number
v-model:value="item.itemStyle.borderRadius"
:min="0"
size="small"
></n-input-number>
<n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' }
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import {
GlobalSetting,
CollapseItem,
SettingItemBox,
SettingItem
} from '@/components/Pages/ChartItemSetting'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
const props = defineProps({

View File

@ -53,17 +53,21 @@ const option = computed(() => {
watch(
() => props.chartConfig.option.dataset,
(newData: { dimensions: any }, oldData) => {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
try {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
} catch (error) {
console.log(error)
}
},
{

View File

@ -5,16 +5,20 @@ import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = ['legend', 'xAxis', 'yAxis']
export const seriesItem = {
type: 'bar',
barWidth: null,
label: {
show: true,
position: 'right',
color: '#fff',
fontSize: 12
},
itemStyle: {
color: null,
borderRadius: 0
}
}
export const option = {
tooltip: {
show: true,
@ -29,7 +33,7 @@ export const option = {
},
xAxis: {
show: true,
type: 'value',
type: 'value'
},
yAxis: {
show: true,

View File

@ -26,6 +26,39 @@
></n-input-number>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number
v-model:value="item.label.fontSize"
size="small"
:min="1"
></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.label.color"
></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' },
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>

View File

@ -52,17 +52,21 @@ const option = computed(() => {
watch(
() => props.chartConfig.option.dataset,
(newData: { dimensions: any }, oldData) => {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
try {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
} catch (error) {
console.log(error)
}
},
{

View File

@ -5,16 +5,23 @@ import { defaultTheme, chartColorsSearch } from '@/settings/chartThemes/index'
import dataJson from './data.json'
export const includes = ['legend', 'xAxis', 'yAxis']
export const seriesItem = {
type: 'line',
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 12
},
symbolSize: 5, //设定实心点的大小
itemStyle: {
color: null,
borderRadius: 0
},
lineStyle: {
type: 'solid',
width: 3,
itemStyle: {
color: null,
borderRadius: 0
}
color: null,
}
}

View File

@ -1,13 +1,11 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"></global-setting>
<CollapseItem
v-for="(item, index) in seriesList"
:key="index"
:name="`折线图-${index + 1}`"
:expanded="true"
>
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true">
<SettingItemBox name="线条">
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.lineStyle.color"></n-color-picker>
</setting-item>
<SettingItem name="宽度">
<n-input-number
v-model:value="item.lineStyle.width"
@ -15,16 +13,48 @@
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
></n-input-number>
</SettingItem>
<SettingItem name="类型">
<n-select
v-model:value="item.lineStyle.type"
size="small"
:options="lineConf.lineStyle.type"
></n-select>
<n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="实心点">
<SettingItem name="大小">
<n-input-number
v-model:value="item.symbolSize"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' }
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>
@ -32,12 +62,7 @@
import { PropType, computed } from 'vue'
import { lineConf } from '@/packages/chartConfiguration/echarts/index'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import {
GlobalSetting,
CollapseItem,
SettingItemBox,
SettingItem
} from '@/components/Pages/ChartItemSetting'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
const props = defineProps({
optionData: {

View File

@ -53,17 +53,21 @@ const option = computed(() => {
watch(
() => props.chartConfig.option.dataset,
(newData: { dimensions: any }, oldData) => {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
try {
if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) {
const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem)
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
}
replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => {
replaceMergeArr.value = []
})
} catch (error) {
console.log(error)
}
},
{

View File

@ -20,7 +20,7 @@ const options = {
},
xAxis: {
show: true,
type: 'category'
type: 'category',
},
yAxis: {
show: true,
@ -31,6 +31,13 @@ const options = {
{
type: 'line',
smooth: false,
symbolSize: 5, //设定实心点的大小
label:{
show: true,
position: 'top',
color: "#fff",
fontSize: 12,
},
lineStyle: {
type: 'solid',
width: 3
@ -52,7 +59,8 @@ const options = {
]
}
export default class Config extends PublicConfigClass implements CreateComponentType {
export default class Config extends PublicConfigClass
implements CreateComponentType {
public key: string = LineGradientSingleConfig.key
public chartConfig = LineGradientSingleConfig
// 图表配置项

View File

@ -1,12 +1,7 @@
<template>
<!-- Echarts 全局设置 -->
<global-setting :optionData="optionData"></global-setting>
<CollapseItem
v-for="(item, index) in seriesList"
:key="index"
name="单折线面积图"
:expanded="true"
>
<CollapseItem v-for="(item, index) in seriesList" :key="index" name="单折线面积图" :expanded="true">
<SettingItemBox name="线条">
<SettingItem name="宽度">
<n-input-number
@ -18,13 +13,45 @@
></n-input-number>
</SettingItem>
<SettingItem name="类型">
<n-select
v-model:value="item.lineStyle.type"
size="small"
:options="lineConf.lineStyle.type"
></n-select>
<n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="实心点">
<SettingItem name="大小">
<n-input-number
v-model:value="item.symbolSize"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' }
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>
@ -32,18 +59,13 @@
import { PropType, computed } from 'vue'
import { lineConf } from '@/packages/chartConfiguration/echarts/index'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
import {
GlobalSetting,
CollapseItem,
SettingItemBox,
SettingItem
} from '@/components/Pages/ChartItemSetting'
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
},
}
})
const seriesList = computed(() => {

View File

@ -43,23 +43,27 @@ const option = reactive({
watch(
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
(newColor: keyof typeof chartColorsSearch) => {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any, index: number) => {
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: themeColor[3]
},
{
offset: 1,
color: 'rgba(0,0,0, 0)'
}
])
})
try {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any, index: number) => {
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: themeColor[3]
},
{
offset: 1,
color: 'rgba(0,0,0, 0)'
}
])
})
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
} catch (error) {
console.log(error)
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
},
{
immediate: true
@ -70,9 +74,6 @@ watch(
() => props.chartConfig.option.dataset,
() => {
option.value = props.chartConfig.option
},
{
deep: false
}
)

View File

@ -31,6 +31,13 @@ const option = {
{
type: 'line',
smooth: false,
symbolSize: 5, //设定实心点的大小
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 12
},
lineStyle: {
width: 3,
type: 'solid'
@ -52,6 +59,12 @@ const option = {
{
type: 'line',
smooth: false,
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 12
},
lineStyle: {
width: 3,
type: 'solid'

View File

@ -12,7 +12,6 @@
<n-input-number
v-model:value="item.lineStyle.width"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
@ -25,6 +24,50 @@
></n-select>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="实心点">
<SettingItem name="大小">
<n-input-number
v-model:value="item.symbolSize"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
</SettingItem>
</SettingItemBox>
<setting-item-box name="标签">
<setting-item>
<n-space>
<n-switch v-model:value="item.label.show" size="small" />
<n-text>展示标签</n-text>
</n-space>
</setting-item>
<setting-item name="大小">
<n-input-number
v-model:value="item.label.fontSize"
size="small"
:min="1"
></n-input-number>
</setting-item>
<setting-item name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="item.label.color"
></n-color-picker>
</setting-item>
<setting-item name="位置">
<n-select
v-model:value="item.label.position"
:options="[
{ label: 'top', value: 'top' },
{ label: 'left', value: 'left' },
{ label: 'right', value: 'right' },
{ label: 'bottom', value: 'bottom' },
]"
/>
</setting-item>
</setting-item-box>
</CollapseItem>
</template>

View File

@ -42,23 +42,27 @@ const option = reactive({
watch(
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
(newColor: keyof typeof chartColorsSearch) => {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any, index: number) => {
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: themeColor[3 + index]
},
{
offset: 1,
color: 'rgba(0,0,0, 0)'
}
])
})
try {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any, index: number) => {
value.areaStyle.color = new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: themeColor[3 + index]
},
{
offset: 1,
color: 'rgba(0,0,0, 0)'
}
])
})
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
} catch (error) {
console.log(error)
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
},
{
immediate: true
@ -71,6 +75,5 @@ watch(
option.value = props.chartConfig.option
}
)
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore)
</script>

View File

@ -29,6 +29,7 @@ export const option = {
series: [
{
type: 'line',
symbolSize: 5, //设定实心点的大小
lineStyle: {
type: 'solid',
width: 3,

View File

@ -39,6 +39,17 @@
></n-select>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="实心点">
<SettingItem name="大小">
<n-input-number
v-model:value="item.symbolSize"
:min="1"
:max="100"
size="small"
placeholder="自动计算"
></n-input-number>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="阴影" :alone="true">
<SettingItem name="颜色">
<n-color-picker

View File

@ -43,17 +43,21 @@ const option = reactive({
watch(
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
(newColor: keyof typeof chartColorsSearch) => {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any) => {
value.lineStyle.shadowColor = themeColor[2]
value.lineStyle.color.colorStops.forEach((v: { color: string }, i: number) => {
v.color = themeColor[i]
try {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
props.chartConfig.option.series.forEach((value: any) => {
value.lineStyle.shadowColor = themeColor[2]
value.lineStyle.color.colorStops.forEach((v: { color: string }, i: number) => {
v.color = themeColor[i]
})
})
})
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
} catch (error) {
console.log(error)
}
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
props.chartConfig.option = option.value
},
{
immediate: true

View File

@ -64,9 +64,9 @@ registerMap(props.chartConfig.option.mapRegion.adcode, { geoJSON: {} as any, spe
// china
const registerMapInitAsync = async () => {
await nextTick()
if (props.chartConfig.option.mapRegion.adcode!="china") {
if (props.chartConfig.option.mapRegion.adcode != 'china') {
await getGeojson(props.chartConfig.option.mapRegion.adcode)
}else{
} else {
await hainanLandsHandle(props.chartConfig.option.mapRegion.showHainanIsLands)
}
vEchartsSetOption()
@ -90,12 +90,12 @@ const dataSetHandle = async (dataset: any) => {
isPreview() && vEchartsSetOption()
}
//
const hainanLandsHandle=async(newData:boolean)=>{
const hainanLandsHandle = async (newData: boolean) => {
if (newData) {
await getGeojson('china')
} else {
registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} })
}
await getGeojson('china')
} else {
registerMap('china', { geoJSON: mapJsonWithoutHainanIsLands as any, specialAreas: {} })
}
}
// dataset
watch(
@ -113,8 +113,12 @@ watch(
watch(
() => props.chartConfig.option.mapRegion.showHainanIsLands,
async newData => {
await hainanLandsHandle(newData)
vEchartsSetOption()
try {
await hainanLandsHandle(newData)
vEchartsSetOption()
} catch (error) {
console.log(error)
}
},
{
deep: false
@ -125,12 +129,16 @@ watch(
watch(
() => props.chartConfig.option.mapRegion.adcode,
async newData => {
await getGeojson(newData)
props.chartConfig.option.geo.map = newData
props.chartConfig.option.series.forEach((item: any) => {
if (item.type === 'map') item.map = newData
})
vEchartsSetOption()
try {
await getGeojson(newData)
props.chartConfig.option.geo.map = newData
props.chartConfig.option.series.forEach((item: any) => {
if (item.type === 'map') item.map = newData
})
vEchartsSetOption()
} catch (error) {
console.log(error)
}
},
{
deep: false

View File

@ -75,7 +75,11 @@ const dataSetHandle = (dataset: typeof dataJson) => {
watch(
() => props.chartConfig.option.dataset,
newData => {
dataSetHandle(newData)
try {
dataSetHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: false

View File

@ -57,7 +57,11 @@ const option = shallowReactive({
watch(
() => props.chartConfig.option.dataset,
(newData: any) => {
option.dataset = toNumber(newData, 2)
try {
option.dataset = toNumber(newData, 2)
} catch (error) {
console.log(error)
}
},
{
deep: false

View File

@ -57,7 +57,11 @@ const dataSetHandle = (dataset: typeof dataJson) => {
watch(
() => props.chartConfig.option.dataset,
newData => {
dataSetHandle(newData)
try {
dataSetHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: false

View File

@ -49,8 +49,12 @@ const dataSetHandle = (dataset: typeof dataJson) => {
watch(
() => props.chartConfig.option.dataset,
newData => {
if(!isArray(newData)) return
dataSetHandle(newData)
try {
if (!isArray(newData)) return
dataSetHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: false

View File

@ -42,23 +42,27 @@ const option = reactive({
watch(
() => chartEditStore.getEditCanvasConfig.chartThemeColor,
(newColor: keyof typeof chartColorsSearch) => {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
//
props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2]
//
props.chartConfig.option.series[0].color[0].colorStops = [
{
offset: 0,
color: themeColor[0]
},
{
offset: 1,
color: themeColor[1]
}
]
try {
if (!isPreview()) {
const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
//
props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2]
//
props.chartConfig.option.series[0].color[0].colorStops = [
{
offset: 0,
color: themeColor[0]
},
{
offset: 1,
color: themeColor[1]
}
]
}
option.value = props.chartConfig.option
} catch (error) {
console.log(error)
}
option.value = props.chartConfig.option
},
{
immediate: true
@ -75,7 +79,7 @@ const dataHandle = (newData: number | string) => {
watch(
() => props.chartConfig.option.dataset,
newData => {
if(!isString(newData) && !isNumber(newData)) return
if (!isString(newData) && !isNumber(newData)) return
props.chartConfig.option.series[0].data = [dataHandle(newData)]
option.value = props.chartConfig.option
},

View File

@ -48,7 +48,13 @@ const dataHandle = (newData: any) => {
//
watch(
() => props.chartConfig.option.dataset,
newData => dataHandle(newData),
newData => {
try {
dataHandle(newData)
} catch (error) {
console.log(error)
}
},
{
immediate: true,
deep: false

View File

@ -39,15 +39,19 @@ const option = computed(() => {
watch(
() => props.chartConfig.option.type,
newData => {
if (newData === 'nomal') {
props.chartConfig.option.series[0].radius = '70%'
props.chartConfig.option.series[0].roseType = false
} else if (newData === 'ring') {
props.chartConfig.option.series[0].radius = ['40%', '65%']
props.chartConfig.option.series[0].roseType = false
} else {
props.chartConfig.option.series[0].radius = '70%'
props.chartConfig.option.series[0].roseType = true
try {
if (newData === 'nomal') {
props.chartConfig.option.series[0].radius = '70%'
props.chartConfig.option.series[0].roseType = false
} else if (newData === 'ring') {
props.chartConfig.option.series[0].radius = ['40%', '65%']
props.chartConfig.option.series[0].roseType = false
} else {
props.chartConfig.option.series[0].radius = '70%'
props.chartConfig.option.series[0].roseType = true
}
} catch (error) {
console.log(error)
}
},
{ deep: false, immediate: true }

View File

@ -69,17 +69,21 @@ const option = computed(() => {
watch(
() => props.chartConfig.option.dataset,
(newData, oldData) => {
if (!isArray(newData)) return
if (Array.isArray(newData)) {
replaceMergeArr.value = ['series']
// eslint-disable-next-line vue/no-mutating-props
props.chartConfig.option.series = newData.map((item: { dimensions: any[] }, index: number) => ({
...seriesItem,
datasetIndex: index
}))
nextTick(() => {
replaceMergeArr.value = []
})
try {
if (!isArray(newData)) return
if (Array.isArray(newData)) {
replaceMergeArr.value = ['series']
// eslint-disable-next-line vue/no-mutating-props
props.chartConfig.option.series = newData.map((item: { dimensions: any[] }, index: number) => ({
...seriesItem,
datasetIndex: index
}))
nextTick(() => {
replaceMergeArr.value = []
})
}
} catch (error) {
console.log(error)
}
},
{

View File

@ -133,10 +133,14 @@ const renderCountdown: CountdownProps['render'] = ({ hours, minutes, seconds })
}
const updateTotalDuration = () => {
countdownActive.value = false
totalDuration.value = useEndDate.value ? endDate.value - new Date().getTime() : dataset.value * 1000
countdownRef.value?.reset && countdownRef.value?.reset()
countdownActive.value = true
try {
countdownActive.value = false
totalDuration.value = useEndDate.value ? endDate.value - new Date().getTime() : dataset.value * 1000
countdownRef.value?.reset && countdownRef.value?.reset()
countdownActive.value = true
} catch (error) {
console.log(error)
}
}
watch(

View File

@ -60,7 +60,11 @@ const updateDatasetHandler = (newVal: string | number) => {
watch(
() => props.chartConfig.option,
newVal => {
updateDatasetHandler((newVal as OptionType).dataset)
try {
updateDatasetHandler((newVal as OptionType).dataset)
} catch (error) {
console.log(error)
}
},
{
immediate: true,

View File

@ -11,23 +11,23 @@
</template>
<script setup lang="ts">
import { PropType, toRefs, ref, reactive, watch, onMounted, onUnmounted } from "vue";
import { CreateComponentType } from "@/packages/index.d";
import { useChartEditStore } from "@/store/modules/chartEditStore/chartEditStore";
import { useChartDataFetch } from "@/hooks";
import { PropType, toRefs, ref, reactive, watch, onMounted, onUnmounted } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { useChartDataFetch } from '@/hooks'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true,
},
});
let yearMonthDay = ref("2021-2-3");
let nowData = ref("08:00:00");
let newData = ref("2021-2-3 08:00:00");
let boxShadow = ref("none");
required: true
}
})
let yearMonthDay = ref('2021-2-3')
let nowData = ref('08:00:00')
let newData = ref('2021-2-3 08:00:00')
let boxShadow = ref('none')
const { w, h } = toRefs(props.chartConfig.attr);
const { w, h } = toRefs(props.chartConfig.attr)
let {
timeColor,
@ -39,54 +39,55 @@ let {
hShadow,
vShadow,
blurShadow,
colorShadow,
} = toRefs(props.chartConfig.option);
colorShadow
} = toRefs(props.chartConfig.option)
watch(
props.chartConfig.option,
() => {
if (props.chartConfig.option.showShadow) {
boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`;
} else {
boxShadow.value = "none";
try {
if (props.chartConfig.option.showShadow) {
boxShadow.value = `${props.chartConfig.option.hShadow}px ${props.chartConfig.option.vShadow}px ${props.chartConfig.option.blurShadow}px ${props.chartConfig.option.colorShadow}`
} else {
boxShadow.value = 'none'
}
} catch (error) {
console.log(error)
}
},
{
immediate: true,
immediate: true
}
);
)
onMounted(() => {
const timer = setInterval(() => {
var datetime = new Date();
var year = datetime.getFullYear();
var month =
datetime.getMonth() + 1 < 10
? "0" + (datetime.getMonth() + 1)
: datetime.getMonth() + 1;
var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
var hh = datetime.getHours(); //
var mm = datetime.getMinutes(); //
var ss = datetime.getSeconds(); //
let time = "";
if (hh < 10) time += "0";
time += hh + ":";
if (mm < 10) time += "0";
time += mm + ":";
if (ss < 10) time += "0";
time += ss;
yearMonthDay.value = `${year}-${month}-${date}`;
nowData.value = time;
newData.value = yearMonthDay.value + " " + nowData.value;
}, 500);
});
var datetime = new Date()
var year = datetime.getFullYear()
var month = datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1
var date = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate()
var hh = datetime.getHours() //
var mm = datetime.getMinutes() //
var ss = datetime.getSeconds() //
let time = ''
if (hh < 10) time += '0'
time += hh + ':'
if (mm < 10) time += '0'
time += mm + ':'
if (ss < 10) time += '0'
time += ss
yearMonthDay.value = `${year}-${month}-${date}`
nowData.value = time
newData.value = yearMonthDay.value + ' ' + nowData.value
}, 500)
})
onUnmounted(() => {
clearInterval();
});
useChartDataFetch(props.chartConfig, useChartEditStore);
clearInterval()
})
useChartDataFetch(props.chartConfig, useChartEditStore)
</script>
<style lang="scss" scoped>
@include go("decorates-number") {
@include go('decorates-number') {
text-align: center;
}
</style>

View File

@ -0,0 +1,236 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import {
ArcCurve,
BufferAttribute,
BufferGeometry,
Color,
Line,
LineBasicMaterial,
Points,
PointsMaterial,
Quaternion,
Vector3
} from 'three'
import { lon2xyz } from './common'
/*
* 线
* 5( 线, , )
*/
function createFlyLine(radius, startAngle, endAngle, color) {
const geometry = new BufferGeometry() //声明一个几何体对象BufferGeometry
// ArcCurve创建圆弧曲线
const arc = new ArcCurve(0, 0, radius, startAngle, endAngle, false)
//getSpacedPoints是基类Curve的方法返回一个vector2对象作为元素组成的数组
const pointsArr = arc.getSpacedPoints(100) //分段数80返回81个顶点
geometry.setFromPoints(pointsArr) // setFromPoints方法从pointsArr中提取数据改变几何体的顶点属性vertices
// 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小
const percentArr = [] //attributes.percent的数据
for (let i = 0; i < pointsArr.length; i++) {
percentArr.push(i / pointsArr.length)
}
const percentAttribue = new BufferAttribute(new Float32Array(percentArr), 1)
// 通过顶点数据percent点模型从大到小变化产生小蝌蚪形状飞线
geometry.attributes.percent = percentAttribue
// 批量计算所有顶点颜色数据
const colorArr = []
for (let i = 0; i < pointsArr.length; i++) {
const color1 = new Color(0xec8f43) //轨迹线颜色 青色
const color2 = new Color(0xf3ae76) //黄色
const color = color1.lerp(color2, i / pointsArr.length)
colorArr.push(color.r, color.g, color.b)
}
// 设置几何体顶点颜色数据
geometry.attributes.color = new BufferAttribute(new Float32Array(colorArr), 3)
const size = 1.3
// 点模型渲染几何体每个顶点
const material = new PointsMaterial({
size, //点大小
// vertexColors: VertexColors, //使用顶点颜色渲染
transparent: true,
depthWrite: false
})
// 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的)
material.onBeforeCompile = function (shader) {
// 顶点着色器中声明一个attribute变量:百分比
shader.vertexShader = shader.vertexShader.replace(
'void main() {',
[
'attribute float percent;', //顶点大小百分比变量,控制点渲染大小
'void main() {'
].join('\n') // .join()把数组元素合成字符串
)
// 调整点渲染大小计算方式
shader.vertexShader = shader.vertexShader.replace(
'gl_PointSize = size;',
['gl_PointSize = percent * size;'].join('\n') // .join()把数组元素合成字符串
)
}
const FlyLine = new Points(geometry, material)
material.color = new Color(color)
FlyLine.name = '飞行线'
return FlyLine
}
/**flyArc线
* lon1,lat1:轨迹线起点经纬度坐标
* lon2,lat2线
*/
function flyArc(radius, lon1, lat1, lon2, lat2, options) {
const sphereCoord1 = lon2xyz(radius, lon1, lat1) //经纬度坐标转球面坐标
// startSphereCoord轨迹线起点球面坐标
const startSphereCoord = new Vector3(sphereCoord1.x, sphereCoord1.y, sphereCoord1.z)
const sphereCoord2 = lon2xyz(radius, lon2, lat2)
// startSphereCoord轨迹线结束点球面坐标
const endSphereCoord = new Vector3(sphereCoord2.x, sphereCoord2.y, sphereCoord2.z)
//计算绘制圆弧需要的关于y轴对称的起点、结束点和旋转四元数
const startEndQua = _3Dto2D(startSphereCoord, endSphereCoord)
// 调用arcXOY函数绘制一条圆弧飞线轨迹
const arcline = arcXOY(radius, startEndQua.startPoint, startEndQua.endPoint, options)
arcline.quaternion.multiply(startEndQua.quaternion)
return arcline
}
/*
* 3D球面上任意的两个飞线起点和结束点绕球心旋转到到XOY平面上
* y轴对称
*
*/
function _3Dto2D(startSphere, endSphere) {
/*计算第一次旋转的四元数:表示从一个平面如何旋转到另一个平面*/
const origin = new Vector3(0, 0, 0) //球心坐标
const startDir = startSphere.clone().sub(origin) //飞线起点与球心构成方向向量
const endDir = endSphere.clone().sub(origin) //飞线结束点与球心构成方向向量
// dir1和dir2构成一个三角形.cross()叉乘计算该三角形法线normal
const normal = startDir.clone().cross(endDir).normalize()
const xoyNormal = new Vector3(0, 0, 1) //XOY平面的法线
//.setFromUnitVectors()计算从normal向量旋转达到xoyNormal向量所需要的四元数
// quaternion表示把球面飞线旋转到XOY平面上需要的四元数
const quaternion3D_XOY = new Quaternion().setFromUnitVectors(normal, xoyNormal)
/*第一次旋转飞线起点、结束点从3D空间第一次旋转到XOY平面*/
const startSphereXOY = startSphere.clone().applyQuaternion(quaternion3D_XOY)
const endSphereXOY = endSphere.clone().applyQuaternion(quaternion3D_XOY)
/*计算第二次旋转的四元数*/
// middleV3startSphereXOY和endSphereXOY的中点
const middleV3 = startSphereXOY.clone().add(endSphereXOY).multiplyScalar(0.5)
const midDir = middleV3.clone().sub(origin).normalize() // 旋转前向量midDir中点middleV3和球心构成的方向向量
const yDir = new Vector3(0, 1, 0) // 旋转后向量yDir即y轴
// .setFromUnitVectors()计算从midDir向量旋转达到yDir向量所需要的四元数
// quaternion2表示让第一次旋转到XOY平面的起点和结束点关于y轴对称需要的四元数
const quaternionXOY_Y = new Quaternion().setFromUnitVectors(midDir, yDir)
/*第二次旋转使旋转到XOY平面的点再次旋转实现关于Y轴对称*/
const startSpherXOY_Y = startSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
const endSphereXOY_Y = endSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
/**
*.invert()
* .invert().multiply()
*.invert().invert()
*/
const quaternionInverse = quaternion3D_XOY.clone().invert().multiply(quaternionXOY_Y.clone().invert())
return {
// 返回两次旋转四元数的逆四元数
quaternion: quaternionInverse,
// 范围两次旋转后在XOY平面上关于y轴对称的圆弧起点和结束点坐标
startPoint: startSpherXOY_Y,
endPoint: endSphereXOY_Y
}
}
/**arcXOY()XOYy线
* startPoint, endPoint线y轴对称
* 线*/
function arcXOY(radius, startPoint, endPoint, options) {
// 计算两点的中点
const middleV3 = new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5)
// 弦垂线的方向dir(弦的中点和圆心构成的向量)
const dir = middleV3.clone().normalize()
// 计算球面飞线的起点、结束点和球心构成夹角的弧度值
const earthRadianAngle = radianAOB(startPoint, endPoint, new Vector3(0, 0, 0))
/*线
* radius * 0.2线
线*/
const arcTopCoord = dir.multiplyScalar(radius + earthRadianAngle * radius * 0.2) // 黄色飞行线的高度
//求三个点的外接圆圆心(飞线圆弧轨迹的圆心坐标)
const flyArcCenter = threePointCenter(startPoint, endPoint, arcTopCoord)
// 飞线圆弧轨迹半径flyArcR
const flyArcR = Math.abs(flyArcCenter.y - arcTopCoord.y)
/*线线y
线y轴负半轴上一点线*/
const flyRadianAngle = radianAOB(startPoint, new Vector3(0, -1, 0), flyArcCenter)
const startAngle = -Math.PI / 2 + flyRadianAngle //飞线圆弧开始角度
const endAngle = Math.PI - startAngle //飞线圆弧结束角度
// 调用圆弧线模型的绘制函数
const arcline = circleLine(flyArcCenter.x, flyArcCenter.y, flyArcR, startAngle, endAngle, options.color)
// const arcline = new Group();// 不绘制轨迹线,使用 Group替换circleLine()即可
arcline.center = flyArcCenter //飞线圆弧自定一个属性表示飞线圆弧的圆心
arcline.topCoord = arcTopCoord //飞线圆弧自定一个属性表示飞线圆弧中间也就是顶部坐标
// const flyAngle = Math.PI/ 10; //飞线圆弧固定弧度
const flyAngle = (endAngle - startAngle) / 7 //飞线圆弧的弧度和轨迹线弧度相关
// 绘制一段飞线,圆心做坐标原点
const flyLine = createFlyLine(flyArcR, startAngle, startAngle + flyAngle, options.flyLineColor)
flyLine.position.y = flyArcCenter.y //平移飞线圆弧和飞线轨迹圆弧重合
//飞线段flyLine作为飞线轨迹arcLine子对象继承飞线轨迹平移旋转等变换
arcline.add(flyLine)
//飞线段运动范围startAngle~flyEndAngle
flyLine.flyEndAngle = endAngle - startAngle - flyAngle
flyLine.startAngle = startAngle
// arcline.flyEndAngle飞线段当前角度位置这里设置了一个随机值用于演示
flyLine.AngleZ = arcline.flyEndAngle * Math.random()
// flyLine.rotation.z = arcline.AngleZ;
// arcline.flyLine指向飞线段,便于设置动画是访问飞线段
arcline.userData['flyLine'] = flyLine
return arcline
}
/*
point1, point2:表示地球球面上两点坐标Vector3
AB两点和顶点O构成的AOB夹角弧度值*/
function radianAOB(A, B, O) {
// dir1、dir2球面上两个点和球心构成的方向向量
const dir1 = A.clone().sub(O).normalize()
const dir2 = B.clone().sub(O).normalize()
//点乘.dot()计算夹角余弦值
const cosAngle = dir1.clone().dot(dir2)
const radianAngle = Math.acos(cosAngle) //余弦值转夹角弧度值,通过余弦值可以计算夹角范围是0~180度
return radianAngle
}
/*线Line
5(, , 线, , )*/
function circleLine(x, y, r, startAngle, endAngle, color) {
const geometry = new BufferGeometry() //声明一个几何体对象Geometry
// ArcCurve创建圆弧曲线
const arc = new ArcCurve(x, y, r, startAngle, endAngle, false)
//getSpacedPoints是基类Curve的方法返回一个vector2对象作为元素组成的数组
const points = arc.getSpacedPoints(80) //分段数50返回51个顶点
geometry.setFromPoints(points) // setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
const material = new LineBasicMaterial({
color: color || 0xd18547
}) //线条材质
const line = new Line(geometry, material) //线条模型对象
return line
}
//求三个点的外接圆圆心p1, p2, p3表示三个点的坐标Vector3。
function threePointCenter(p1, p2, p3) {
const L1 = p1.lengthSq() //p1到坐标原点距离的平方
const L2 = p2.lengthSq()
const L3 = p3.lengthSq()
const x1 = p1.x,
y1 = p1.y,
x2 = p2.x,
y2 = p2.y,
x3 = p3.x,
y3 = p3.y
const S = x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2
const x = (L2 * y3 + L1 * y2 + L3 * y1 - L2 * y1 - L3 * y2 - L1 * y3) / S / 2
const y = (L3 * x2 + L2 * x1 + L1 * x3 - L1 * x2 - L2 * x3 - L3 * x1) / S / 2
// 三点外接圆圆心坐标
const center = new Vector3(x, y, 0)
return center
}
export { arcXOY, flyArc }

View File

@ -0,0 +1,137 @@
import {
CatmullRomCurve3,
DoubleSide,
Group,
Mesh,
MeshBasicMaterial,
PlaneGeometry,
Texture,
TubeGeometry,
Vector3
} from 'three'
import { punctuation } from '../world/Earth'
/**
*
* @param {} R
* @param {()} longitude
* @param {()} latitude
*/
export const lon2xyz = (R: number, longitude: number, latitude: number): Vector3 => {
let lon = (longitude * Math.PI) / 180 // 转弧度值
const lat = (latitude * Math.PI) / 180 // 转弧度值
lon = -lon // js坐标系z坐标轴对应经度-90度而不是90度
// 经纬度坐标转球面坐标计算公式
const x = R * Math.cos(lat) * Math.cos(lon)
const y = R * Math.sin(lat)
const z = R * Math.cos(lat) * Math.sin(lon)
// 返回球面坐标
return new Vector3(x, y, z)
}
// 创建波动光圈
export const createWaveMesh = (options: { radius: number; lon: number; lat: number; textures: any }) => {
const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
const texture = options.textures.aperture
const material = new MeshBasicMaterial({
color: 0xe99f68,
map: texture,
transparent: true, //使用背景透明的png贴图注意开启透明计算
opacity: 1.0,
depthWrite: false //禁止写入深度缓冲区数据
})
const mesh = new Mesh(geometry, material)
// 经纬度转球面坐标
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
const size = options.radius * 0.12 //矩形平面Mesh的尺寸
mesh.scale.set(size, size, size) //设置mesh大小
mesh.userData['size'] = size //自顶一个属性表示mesh静态大小
mesh.userData['scale'] = Math.random() * 1.0 //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~2倍之间变化
mesh.position.set(coord.x, coord.y, coord.z)
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
const meshNormal = new Vector3(0, 0, 1)
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
return mesh
}
// 创建柱状
export const createLightPillar = (options: {
radius: number
lon: number
lat: number
index: number
textures: Record<string, Texture>
punctuation: punctuation
}) => {
const height = options.radius * 0.3
const geometry = new PlaneGeometry(options.radius * 0.05, height)
geometry.rotateX(Math.PI / 2)
geometry.translate(0, 0, height / 2)
const material = new MeshBasicMaterial({
map: options.textures.light_column,
color: options.index == 0 ? options.punctuation.lightColumn.startColor : options.punctuation.lightColumn.endColor,
transparent: true,
side: DoubleSide,
depthWrite: false //是否对深度缓冲区有任何的影响
})
const mesh = new Mesh(geometry, material)
const group = new Group()
// 两个光柱交叉叠加
group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)) //几何体绕x轴旋转了所以mesh旋转轴变为z
// 经纬度转球面坐标
const SphereCoord = lon2xyz(options.radius, options.lon, options.lat) //SphereCoord球面坐标
group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z) //设置mesh位置
const coordVec3 = new Vector3(SphereCoord.x, SphereCoord.y, SphereCoord.z).normalize()
const meshNormal = new Vector3(0, 0, 1)
group.quaternion.setFromUnitVectors(meshNormal, coordVec3)
return group
}
// 光柱底座矩形平面
export const createPointMesh = (options: { radius: number; lon: number; lat: number; material: MeshBasicMaterial }) => {
const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
const mesh = new Mesh(geometry, options.material)
// 经纬度转球面坐标
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
const size = options.radius * 0.05 // 矩形平面Mesh的尺寸
mesh.scale.set(size, size, size) // 设置mesh大小
// 设置mesh位置
mesh.position.set(coord.x, coord.y, coord.z)
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
const meshNormal = new Vector3(0, 0, 1)
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
return mesh
}
// 获取点
export const getCirclePoints = (option: any) => {
const list = []
for (let j = 0; j < 2 * Math.PI - 0.1; j += (2 * Math.PI) / (option.number || 100)) {
list.push([
parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)),
0,
parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2))
])
}
if (option.closed) list.push(list[0])
return list
}
// 创建线
/**
* 线
*/
export const createAnimateLine = (option: any) => {
// 由多个点数组构成的曲线 通常用于道路
const l: Array<any> = []
option.pointList.forEach((e: Array<any>) => l.push(new Vector3(e[0], e[1], e[2])))
const curve = new CatmullRomCurve3(l) // 曲线路径
// 管道体
const tubeGeometry = new TubeGeometry(curve, option.number || 50, option.radius || 1, option.radialSegments)
return new Mesh(tubeGeometry, option.material)
}

View File

@ -0,0 +1,4 @@
export interface IEvents {
resize: () => void
}

View File

@ -0,0 +1,6 @@
export interface IWord {
dom: HTMLElement
data: any
width: number
height: number
}

View File

@ -0,0 +1,23 @@
uniform vec3 glowColor;
uniform float bias;
uniform float power;
uniform float time;
varying vec3 vp;
varying vec3 vNormal;
varying vec3 vPositionNormal;
uniform float scale;
//
uniform sampler2D map;
//
varying vec2 vUv;
void main(void){
float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power );
if(vp.y > time && vp.y < time + 20.0) {
float t = smoothstep(0.0, 0.8, (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0 );
gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t );
}
gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a);
float b = 0.8;
gl_FragColor = gl_FragColor + texture2D( map, vUv );
}

View File

@ -0,0 +1,12 @@
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vp;
varying vec3 vPositionNormal;
void main(void){
vUv = uv;
vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
vp = position;
vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

View File

@ -0,0 +1,34 @@
/**
*
*
*/
interface ITextures {
name: string
url: string
}
export interface IResources {
textures?: ITextures[]
}
const fileSuffix = ['earth', 'gradient', 'redCircle', 'label', 'aperture', 'glow', 'light_column', 'aircraft']
const textures: ITextures[] = []
const modules = import.meta.globEager("../../images/earth/*");
for(let item in modules) {
const n = item.split('/').pop()
if(n) {
textures.push({
name: n.split('.')[0],
url: modules[item].default
})
}
}
const resources: IResources = {
textures
}
export { resources }

View File

@ -0,0 +1,62 @@
/**
* threejs
*
*/
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
export class Basic {
public scene!: THREE.Scene
public camera!: THREE.PerspectiveCamera
public renderer!: THREE.WebGLRenderer
public controls!: OrbitControls
public dom: HTMLElement
constructor(dom: HTMLElement) {
this.dom = dom
this.initScenes()
this.setControls()
}
/**
*
*/
initScenes() {
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000)
this.camera.position.set(0, 30, -250)
this.renderer = new THREE.WebGLRenderer({
// canvas: this.dom,
alpha: true, // 透明
antialias: true // 抗锯齿
})
this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比
this.renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器宽高
this.dom.appendChild(this.renderer.domElement) // 添加到dom中
}
/**
*
*/
setControls() {
// 鼠标控制 相机渲染dom
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.autoRotateSpeed = 3
// 使动画循环使用时阻尼或自转 意思是否有惯性
this.controls.enableDamping = true
// 动态阻尼系数 就是鼠标拖拽旋转灵敏度
this.controls.dampingFactor = 0.05
// 是否可以缩放
this.controls.enableZoom = true
// 设置相机距离原点的最远距离
this.controls.minDistance = 100
// 设置相机距离原点的最远距离
this.controls.maxDistance = 300
// 是否开启右键拖拽
this.controls.enablePan = false
}
}

View File

@ -0,0 +1,496 @@
import {
BufferAttribute,
BufferGeometry,
Color,
DoubleSide,
Group,
Material,
Mesh,
MeshBasicMaterial,
NormalBlending,
Object3D,
Points,
PointsMaterial,
ShaderMaterial,
SphereGeometry,
Sprite,
SpriteMaterial,
Texture,
TextureLoader,
Vector3
} from 'three'
import {
createAnimateLine,
createLightPillar,
createPointMesh,
createWaveMesh,
getCirclePoints,
lon2xyz
} from '../Utils/common'
import gsap from 'gsap'
import { flyArc } from '../Utils/arc'
import earthVertex from '../shaders/earth/vertex.vs?raw'
import earthFragment from '../shaders/earth/fragment.fs?raw'
export type punctuation = {
circleColor: number
lightColumn: {
startColor: number // 起点颜色
endColor: number // 终点颜色
}
}
type options = {
data: {
startArray: {
name: string
E: number // 经度
N: number // 维度
}
endArray: {
name: string
E: number // 经度
N: number // 维度
}[]
}[]
dom: HTMLElement
textures: Record<string, Texture> // 贴图
earth: {
radius: number // 地球半径
rotateSpeed: number // 地球旋转速度
isRotation: boolean // 地球组是否自转
}
satellite: {
show: boolean // 是否显示卫星
rotateSpeed: number // 旋转速度
size: number // 卫星大小
number: number // 一个圆环几个球
}
punctuation: punctuation
flyLine: {
color: number // 飞线的颜色
speed: number // 飞机拖尾线速度
flyLineColor: number // 飞行线的颜色
}
}
type uniforms = {
glowColor: { value: Color }
scale: { type: string; value: number }
bias: { type: string; value: number }
power: { type: string; value: number }
time: { type: string; value: any }
isHover: { value: boolean }
map: { value?: Texture }
}
export default class earth {
public group: Group
public earthGroup: Group
public around!: BufferGeometry
public aroundPoints!: Points<BufferGeometry, PointsMaterial>
public options: options
public uniforms: uniforms
public timeValue: number
public earth!: Mesh<SphereGeometry, ShaderMaterial>
public punctuationMaterial!: MeshBasicMaterial
public markupPoint: Group
public waveMeshArr: Object3D[]
public circleLineList: any[]
public circleList: any[]
public x: number
public n: number
public isRotation: boolean
public flyLineArcGroup!: Group
constructor(options: options) {
this.options = options
this.group = new Group()
this.group.name = 'group'
this.group.scale.set(0, 0, 0)
this.earthGroup = new Group()
this.group.add(this.earthGroup)
this.earthGroup.name = 'EarthGroup'
// 标注点效果
this.markupPoint = new Group()
this.markupPoint.name = 'markupPoint'
this.waveMeshArr = []
// 卫星和标签
this.circleLineList = []
this.circleList = []
this.x = 0
this.n = 0
// 地球自转
this.isRotation = this.options.earth.isRotation
// 扫光动画 shader
this.timeValue = 200
this.uniforms = {
glowColor: {
value: new Color(0x0cd1eb)
},
scale: {
type: 'f',
value: -1.0
},
bias: {
type: 'f',
value: 1.0
},
power: {
type: 'f',
value: 3.3
},
time: {
type: 'f',
value: this.timeValue
},
isHover: {
value: false
},
map: {
value: undefined
}
}
}
async init(): Promise<void> {
return new Promise(resolve => {
const init = async () => {
this.createEarth() // 创建地球
this.createEarthGlow() // 创建地球辉光
this.createEarthAperture() // 创建地球的大气层
await this.createMarkupPoint() // 创建柱状点位
this.createAnimateCircle() // 创建环绕卫星
this.createFlyLine() // 创建飞线
this.show()
resolve()
}
init()
})
}
createEarth() {
const earth_geometry = new SphereGeometry(this.options.earth.radius, 50, 50)
const earth_border = new SphereGeometry(this.options.earth.radius + 10, 60, 60)
const pointMaterial = new PointsMaterial({
color: 0x81ffff, //设置颜色,默认 0xFFFFFF
transparent: true,
sizeAttenuation: true,
opacity: 0.1,
vertexColors: false, //定义材料是否使用顶点颜色默认false ---如果该选项设置为true则color属性失效
size: 0.2 //定义粒子的大小。默认为1.0
})
const points = new Points(earth_border, pointMaterial) //将模型添加到场景
this.earthGroup.add(points)
this.uniforms.map.value = this.options.textures.earth
const earth_material = new ShaderMaterial({
// wireframe:true, // 显示模型线条
uniforms: this.uniforms as any,
vertexShader: earthVertex,
fragmentShader: earthFragment
})
earth_material.needsUpdate = true
this.earth = new Mesh(earth_geometry, earth_material)
this.earth.name = 'earth'
this.earthGroup.add(this.earth)
}
createEarthGlow() {
const R = this.options.earth.radius //地球半径
// TextureLoader创建一个纹理加载器对象可以加载图片作为纹理贴图
const texture = this.options.textures.glow // 加载纹理贴图
// 创建精灵材质对象SpriteMaterial
const spriteMaterial = new SpriteMaterial({
map: texture, // 设置精灵纹理贴图
color: 0x4390d1,
transparent: true, //开启透明
opacity: 0.7, // 可以通过透明度整体调节光圈
depthWrite: false //禁止写入深度缓冲区数据
})
// 创建表示地球光圈的精灵模型
const sprite = new Sprite(spriteMaterial)
sprite.scale.set(R * 3.0, R * 3.0, 1) //适当缩放精灵
this.earthGroup.add(sprite)
}
createEarthAperture() {
const vertexShader = [
'varying vec3 vVertexWorldPosition;',
'varying vec3 vVertexNormal;',
'varying vec4 vFragColor;',
'void main(){',
' vVertexNormal = normalize(normalMatrix * normal);', //将法线转换到视图坐标系中
' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', //将顶点转换到世界坐标系中
' // set gl_Position',
' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
'}'
].join('\n')
//大气层效果
const AeroSphere = {
uniforms: {
coeficient: {
type: 'f',
value: 1.0
},
power: {
type: 'f',
value: 3
},
glowColor: {
type: 'c',
value: new Color(0x4390d1)
}
},
vertexShader: vertexShader,
fragmentShader: [
'uniform vec3 glowColor;',
'uniform float coeficient;',
'uniform float power;',
'varying vec3 vVertexNormal;',
'varying vec3 vVertexWorldPosition;',
'varying vec4 vFragColor;',
'void main(){',
' vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机位置到顶点位置的距离
' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', //视图坐标系中从相机位置到顶点位置的距离
' viewCameraToVertex= normalize(viewCameraToVertex);', //规一化
' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);',
' gl_FragColor = vec4(glowColor, intensity);',
'}'
].join('\n')
}
//球体 辉光 大气层
const material1 = new ShaderMaterial({
uniforms: AeroSphere.uniforms,
vertexShader: AeroSphere.vertexShader,
fragmentShader: AeroSphere.fragmentShader,
blending: NormalBlending,
transparent: true,
depthWrite: false
})
const sphere = new SphereGeometry(this.options.earth.radius + 0, 50, 50)
const mesh = new Mesh(sphere, material1)
this.earthGroup.add(mesh)
}
async createMarkupPoint() {
await Promise.all(
this.options.data.map(async item => {
const radius = this.options.earth.radius
const lon = item.startArray.E //经度
const lat = item.startArray.N //纬度
this.punctuationMaterial = new MeshBasicMaterial({
color: this.options.punctuation.circleColor,
map: this.options.textures.label,
transparent: true, //使用背景透明的png贴图注意开启透明计算
depthWrite: false //禁止写入深度缓冲区数据
})
const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
this.markupPoint.add(mesh)
const LightPillar = createLightPillar({
radius: this.options.earth.radius,
lon,
lat,
index: 0,
textures: this.options.textures,
punctuation: this.options.punctuation
}) //光柱
this.markupPoint.add(LightPillar)
const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
this.markupPoint.add(WaveMesh)
this.waveMeshArr.push(WaveMesh)
await Promise.all(
item.endArray.map(obj => {
const lon = obj.E //经度
const lat = obj.N //纬度
const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
this.markupPoint.add(mesh)
const LightPillar = createLightPillar({
radius: this.options.earth.radius,
lon,
lat,
index: 1,
textures: this.options.textures,
punctuation: this.options.punctuation
}) //光柱
this.markupPoint.add(LightPillar)
const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
this.markupPoint.add(WaveMesh)
this.waveMeshArr.push(WaveMesh)
})
)
this.earthGroup.add(this.markupPoint)
})
)
}
createAnimateCircle() {
// 创建 圆环 点
const list = getCirclePoints({
radius: this.options.earth.radius + 15,
number: 150, //切割数
closed: true // 闭合
})
const mat = new MeshBasicMaterial({
color: '#0c3172',
transparent: true,
opacity: 0.4,
side: DoubleSide
})
const line = createAnimateLine({
pointList: list,
material: mat,
number: 100,
radius: 0.1
})
this.earthGroup.add(line)
// 在clone两条线出来
const l2 = line.clone()
l2.scale.set(1.2, 1.2, 1.2)
l2.rotateZ(Math.PI / 6)
this.earthGroup.add(l2)
const l3 = line.clone()
l3.scale.set(0.8, 0.8, 0.8)
l3.rotateZ(-Math.PI / 6)
this.earthGroup.add(l3)
/**
*
*/
const ball = new Mesh(
new SphereGeometry(this.options.satellite.size, 32, 32),
new MeshBasicMaterial({
color: '#e0b187' // 745F4D
})
)
const ball2 = new Mesh(
new SphereGeometry(this.options.satellite.size, 32, 32),
new MeshBasicMaterial({
color: '#628fbb' // 324A62
})
)
const ball3 = new Mesh(
new SphereGeometry(this.options.satellite.size, 32, 32),
new MeshBasicMaterial({
color: '#806bdf' //6D5AC4
})
)
this.circleLineList.push(line, l2, l3)
ball.name = ball2.name = ball3.name = '卫星'
for (let i = 0; i < this.options.satellite.number; i++) {
const ball01 = ball.clone()
// 一根线上总共有几个球,根据数量平均分布一下
const num = Math.floor(list.length / this.options.satellite.number)
ball01.position.set(list[num * (i + 1)][0] * 1, list[num * (i + 1)][1] * 1, list[num * (i + 1)][2] * 1)
line.add(ball01)
const ball02 = ball2.clone()
const num02 = Math.floor(list.length / this.options.satellite.number)
ball02.position.set(list[num02 * (i + 1)][0] * 1, list[num02 * (i + 1)][1] * 1, list[num02 * (i + 1)][2] * 1)
l2.add(ball02)
const ball03 = ball2.clone()
const num03 = Math.floor(list.length / this.options.satellite.number)
ball03.position.set(list[num03 * (i + 1)][0] * 1, list[num03 * (i + 1)][1] * 1, list[num03 * (i + 1)][2] * 1)
l3.add(ball03)
}
}
createFlyLine() {
this.flyLineArcGroup = new Group()
this.flyLineArcGroup.userData['flyLineArray'] = []
this.earthGroup.add(this.flyLineArcGroup)
this.options.data.forEach(cities => {
cities.endArray.forEach(item => {
// 调用函数flyArc绘制球面上任意两点之间飞线圆弧轨迹
const arcline = flyArc(
this.options.earth.radius,
cities.startArray.E,
cities.startArray.N,
item.E,
item.N,
this.options.flyLine
)
this.flyLineArcGroup.add(arcline) // 飞线插入flyArcGroup中
this.flyLineArcGroup.userData['flyLineArray'].push(arcline.userData['flyLine'])
})
})
}
show() {
gsap.to(this.group.scale, {
x: 1,
y: 1,
z: 1,
duration: 2,
ease: 'Quadratic'
})
}
render() {
this.flyLineArcGroup?.userData['flyLineArray']?.forEach((fly: any) => {
fly.rotation.z += this.options.flyLine.speed // 调节飞线速度
if (fly.rotation.z >= fly.flyEndAngle) fly.rotation.z = 0
})
if (this.isRotation) {
this.earthGroup.rotation.y += this.options.earth.rotateSpeed
}
this.circleLineList.forEach(e => {
e.rotateY(this.options.satellite.rotateSpeed)
})
this.uniforms.time.value =
this.uniforms.time.value < -this.timeValue ? this.timeValue : this.uniforms.time.value - 1
if (this.waveMeshArr.length) {
this.waveMeshArr.forEach((mesh: any) => {
mesh.userData['scale'] += 0.007
mesh.scale.set(
mesh.userData['size'] * mesh.userData['scale'],
mesh.userData['size'] * mesh.userData['scale'],
mesh.userData['size'] * mesh.userData['scale']
)
if (mesh.userData['scale'] <= 1.5) {
(mesh.material as Material).opacity = (mesh.userData['scale'] - 1) * 2 //2等于1/(1.5-1.0)保证透明度在0~1之间变化
} else if (mesh.userData['scale'] > 1.5 && mesh.userData['scale'] <= 2) {
(mesh.material as Material).opacity = 1 - (mesh.userData['scale'] - 1.5) * 2 //2等于1/(2.0-1.5) mesh缩放2倍对应0 缩放1.5被对应1
} else {
mesh.userData['scale'] = 1
}
})
}
}
}

View File

@ -0,0 +1,54 @@
/**
*
*/
import { LoadingManager, Texture, TextureLoader } from 'three'
import { loadingStart, loadingFinish, loadingError } from '@/utils'
import { resources } from './Assets'
export class Resources {
private manager!: LoadingManager
private callback: () => void
private textureLoader!: InstanceType<typeof TextureLoader>
public textures: Record<string, Texture>
constructor(callback: () => void) {
this.callback = callback // 资源加载完成的回调
this.textures = {} // 贴图对象
this.setLoadingManager()
this.loadResources()
}
/**
*
*/
private setLoadingManager() {
this.manager = new LoadingManager()
// 开始加载
this.manager.onStart = () => {
loadingStart()
}
// 加载完成
this.manager.onLoad = () => {
this.callback()
}
// 正在进行中
this.manager.onProgress = url => {
loadingFinish()
}
this.manager.onError = url => {
loadingError()
window['$message'].error('数据加载失败,请刷新重试!')
}
}
/**
*
*/
private loadResources(): void {
this.textureLoader = new TextureLoader(this.manager)
resources.textures?.forEach(item => {
this.textureLoader.load(item.url, t => {
this.textures[item.name] = t
})
})
}
}

View File

@ -0,0 +1,112 @@
import { MeshBasicMaterial, PerspectiveCamera, Scene, ShaderMaterial, WebGLRenderer } from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// interfaces
import { IWord } from '../interfaces/IWord'
import { Basic } from './Basic'
import { Resources } from './Resources'
// earth
import Earth from './Earth'
export default class World {
public basic: Basic
public scene: Scene
public camera: PerspectiveCamera
public renderer: WebGLRenderer
public controls: OrbitControls
public material!: ShaderMaterial | MeshBasicMaterial
public resources: Resources
public option: IWord
public earth!: Earth
constructor(option: IWord) {
/**
*
*/
this.option = option
this.basic = new Basic(option.dom)
this.scene = this.basic.scene
this.renderer = this.basic.renderer
this.controls = this.basic.controls
this.camera = this.basic.camera
this.updateSize()
this.resources = new Resources(async () => {
await this.createEarth()
// 开始渲染
this.render()
})
}
async createEarth(data?: any) {
// 资源加载完成开始制作地球注释在new Earth()类型里面
this.earth = new Earth({
data: data || this.option.data,
dom: this.option.dom,
textures: this.resources.textures,
earth: {
radius: 50,
rotateSpeed: 0.002,
isRotation: true
},
satellite: {
show: true,
rotateSpeed: -0.01,
size: 1,
number: 2
},
punctuation: {
circleColor: 0x3892ff,
lightColumn: {
startColor: 0xe4007f, // 起点颜色
endColor: 0xffffff // 终点颜色
}
},
flyLine: {
color: 0xf3ae76, // 飞线的颜色
flyLineColor: 0xff7714, // 飞行线的颜色
speed: 0.004 // 拖尾飞线的速度
}
})
this.scene.add(this.earth.group)
await this.earth.init()
}
/**
*
*/
public render() {
requestAnimationFrame(this.render.bind(this))
this.renderer.render(this.scene, this.camera)
this.controls && this.controls.update()
this.earth && this.earth.render()
}
// 更新
public updateSize(width?: number, height?: number) {
let w = width || this.option.width
let h = height || this.option.height
// 取小值
if (w < h) h = w
else w = h
this.renderer.setSize(w, h)
this.camera.aspect = w / h
this.camera.updateProjectionMatrix()
}
// 数据更新重新渲染
public updateData(data?: any) {
if (!this.earth.group) return
// 先删除旧的
this.scene.remove(this.earth.group)
// 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存
this.earth.group.traverse((obj: any) => {
if (obj.type === 'Mesh') {
obj.geometry.dispose()
obj.material.dispose()
}
})
// 重新创建
this.createEarth(data)
}
}

View File

@ -0,0 +1,17 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { chartInitConfig } from '@/settings/designSetting'
import { ThreeEarth01Config } from './index'
import dataJson from './data.json'
import cloneDeep from 'lodash/cloneDeep'
export const option = {
dataset: dataJson
}
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = ThreeEarth01Config.key
public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 }
public chartConfig = cloneDeep(ThreeEarth01Config)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,14 @@
<template></template>
<script setup lang="ts">
import { PropType } from 'vue'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { option } from './config'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option>,
required: true
}
})
</script>

View File

@ -0,0 +1,84 @@
[
{
"startArray": {
"name": "杭州",
"N": 30.246026,
"E": 120.210792
},
"endArray": [
{
"name": "曼谷",
"N": 22,
"E": 100.49074172973633
},
{
"name": "澳大利亚",
"N": -23.68477416688374,
"E": 133.857421875
},
{
"name": "新疆维吾尔自治区",
"N": 41.748,
"E": 84.9023
},
{
"name": "德黑兰",
"N": 35,
"E": 51
},
{
"name": "德黑兰",
"N": 35,
"E": 51
},
{
"name": "美国",
"N": 34.125447565116126,
"E": 241.7431640625
},
{
"name": "英国",
"N": 51.508742458803326,
"E": 359.82421875
},
{
"name": "巴西",
"N": -9.96885060854611,
"E": 668.1445312499999
}
]
},
{
"startArray": {
"name": "北京",
"N": 39.89491,
"E": 116.322056
},
"endArray": [
{
"name": "西藏",
"N": 29.660361,
"E": 91.132212
},
{
"name": "广西",
"N": 22.830824,
"E": 108.30616
},
{
"name": "江西",
"N": 28.676493,
"E": 115.892151
},
{
"name": "贵阳",
"N": 26.647661,
"E": 106.630153
}
]
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,15 @@
import image from '@/assets/images/chart/decorates/threeEarth01.png'
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const ThreeEarth01Config: ConfigType = {
key: 'ThreeEarth01',
chartKey: 'VThreeEarth01',
conKey: 'VCThreeEarth01',
title: '三维地球',
category: ChatCategoryEnum.THREE,
categoryName: ChatCategoryEnumName.THREE,
package: PackagesCategoryEnum.DECORATES,
chartFrame: ChartFrameEnum.STATIC,
image
}

View File

@ -0,0 +1,81 @@
<template>
<div ref="chartRef"></div>
</template>
<script setup lang="ts">
import { onMounted, PropType, ref, toRefs, watch } from 'vue'
import { CreateComponentType } from '@/packages/index.d'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { option } from './config'
import World from './code/world/Word'
import throttle from 'lodash/throttle'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType & typeof option>,
required: true
}
})
const chartEditStore = useChartEditStore()
const chartRef = ref<HTMLElement>()
const { w, h } = toRefs(props.chartConfig.attr)
let threeClassInstance: World
//
const init = () => {
const dom: HTMLElement | undefined = chartRef.value
if (dom) {
threeClassInstance = new World({
dom: dom,
data: props.chartConfig.option.dataset,
width: w.value,
height: h.value
})
}
}
const updateData = (data: any) => {
try {
threeClassInstance.updateData(data)
} catch (error) {
console.log(error)
}
}
//
watch(
() => [w.value, h.value],
throttle(([newWidth], [newHeight]) => {
threeClassInstance.updateSize(newWidth, newHeight)
}, 100)
)
watch(
() => props.chartConfig.option.dataset,
(newData: any) => {
updateData(newData)
},
{
deep: false
}
)
// DOM
onMounted(() => {
try {
if (navigator.userAgent.indexOf('Chrome') < -1 || navigator.userAgent.indexOf('Edg') > -1) {
window['$message'].error('此组件仅在【谷歌】浏览器上能正常展示!')
chartEditStore.removeComponentList(undefined, false)
return
}
init()
} catch (error) {
console.log(error)
}
})
useChartDataFetch(props.chartConfig, useChartEditStore, updateData)
</script>

View File

@ -0,0 +1,3 @@
import { ThreeEarth01Config } from './ThreeEarth01/index'
export default [ThreeEarth01Config]

View File

@ -1,11 +1,13 @@
export enum ChatCategoryEnum {
BORDER = 'Borders',
DECORATE = 'Decorates',
THREE = 'Three',
MORE = 'Mores'
}
export enum ChatCategoryEnumName {
BORDER = '边框',
DECORATE = '装饰',
THREE = '三维',
MORE = '更多'
}

View File

@ -1,5 +1,6 @@
import Borders from './Borders'
import Decorates from './Decorates'
import Three from './Three'
import Mores from './Mores'
export const DecorateList = [...Borders, ...Decorates, ...Mores]
export const DecorateList = [...Borders, ...Decorates, ...Three, ...Mores]

View File

@ -47,9 +47,12 @@ const option = computed(() => {
})
const dataSetHandle = (dataset: typeof dataJson) => {
dataset && (props.chartConfig.option.series[0].data = dataset)
vChartRef.value && isPreview() && vChartRef.value.setOption(props.chartConfig.option)
try {
dataset && (props.chartConfig.option.series[0].data = dataset)
vChartRef.value && isPreview() && vChartRef.value.setOption(props.chartConfig.option)
} catch (error) {
console.log(error)
}
}
// dataset

View File

@ -10,19 +10,12 @@
<div class="rank" :style="`color: ${color};font-size: ${indexFontSize}px`">No.{{ item.ranking }}</div>
<div class="info-name" :style="`font-size: ${leftFontSize}px`" v-html="item.name" />
<div class="ranking-value" :style="`color: ${textColor};font-size: ${rightFontSize}px`">
{{
status.mergedConfig.valueFormatter
? status.mergedConfig.valueFormatter(item)
: item.value
}}
{{ status.mergedConfig.valueFormatter ? status.mergedConfig.valueFormatter(item) : item.value }}
{{ unit }}
</div>
</div>
<div class="ranking-column" :style="`border-color: ${borderColor}`">
<div
class="inside-column"
:style="`width: ${item.percent}%;background-color: ${color}`"
>
<div class="inside-column" :style="`width: ${item.percent}%;background-color: ${color}`">
<div class="shine" />
</div>
</div>
@ -39,8 +32,8 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true,
},
required: true
}
})
const { w, h } = toRefs(props.chartConfig.attr)
const { rowNum, unit, color, textColor, borderColor, indexFontSize, leftFontSize, rightFontSize } = toRefs(
@ -50,13 +43,15 @@ const { rowNum, unit, color, textColor, borderColor, indexFontSize, leftFontSize
const status = reactive({
mergedConfig: props.chartConfig.option,
rowsData: [],
rows: [{
scroll: 0,
ranking: 1,
name: '',
value: '',
percent: 0
}],
rows: [
{
scroll: 0,
ranking: 1,
name: '',
value: '',
percent: 0
}
],
heights: [0],
animationIndex: 0,
animationHandler: 0,
@ -81,16 +76,16 @@ const calcRowsData = () => {
// abs of max
const maxAbs = Math.abs(max)
const total = max + minAbs
dataset = dataset.map((row: any, i:number) => ({
dataset = dataset.map((row: any, i: number) => ({
...row,
ranking: i + 1,
percent: ((row.value + minAbs) / total) * 100,
percent: ((row.value + minAbs) / total) * 100
}))
const rowLength = dataset.length
if (rowLength > rowNum && rowLength < 2 * rowNum) {
dataset = [...dataset, ...dataset]
}
dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i }))
dataset = dataset.map((d: any, i: number) => ({ ...d, scroll: i }))
status.rowsData = dataset
status.rows = dataset
}
@ -134,11 +129,15 @@ const stopAnimation = () => {
}
const onRestart = async () => {
if (!status.mergedConfig) return
stopAnimation()
calcRowsData()
calcHeights(true)
animation(true)
try {
if (!status.mergedConfig) return
stopAnimation()
calcRowsData()
calcHeights(true)
animation(true)
} catch (error) {
console.log(error)
}
}
onRestart()

View File

@ -1,24 +1,47 @@
<template>
<div class="dv-scroll-board">
<div class="header" v-if="status.header.length && status.mergedConfig"
:style="`background-color: ${status.mergedConfig.headerBGC};`">
<div class="header-item" v-for="(headerItem, i) in status.header" :key="`${headerItem}${i}`" :style="`
<div
class="header"
v-if="status.header.length && status.mergedConfig"
:style="`background-color: ${status.mergedConfig.headerBGC};`"
>
<div
class="header-item"
v-for="(headerItem, i) in status.header"
:key="`${headerItem}${i}`"
:style="`
height: ${status.mergedConfig.headerHeight}px;
line-height: ${status.mergedConfig.headerHeight}px;
width: ${status.widths[i]}px;
`" :align="status.aligns[i]" v-html="headerItem" />
`"
:align="status.aligns[i]"
v-html="headerItem"
/>
</div>
<div v-if="status.mergedConfig" class="rows"
:style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`">
<div class="row-item" v-for="(row, ri) in status.rows" :key="`${row.toString()}${row.scroll}`" :style="`
<div
v-if="status.mergedConfig"
class="rows"
:style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`"
>
<div
class="row-item"
v-for="(row, ri) in status.rows"
:key="`${row.toString()}${row.scroll}`"
:style="`
height: ${status.heights[ri]}px;
line-height: ${status.heights[ri]}px;
background-color: ${status.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']};
`">
<div class="ceil" v-for="(ceil, ci) in row.ceils" :key="`${ceil}${ri}${ci}`"
:style="`width: ${status.widths[ci]}px;`" :align="status.aligns[ci]" v-html="ceil" />
`"
>
<div
class="ceil"
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
:style="`width: ${status.widths[ci]}px;`"
:align="status.aligns[ci]"
v-html="ceil"
/>
</div>
</div>
</div>
@ -35,8 +58,8 @@ import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true,
},
required: true
}
})
//
@ -138,11 +161,13 @@ const status = reactive({
mergedConfig: props.chartConfig.option,
header: [],
rowsData: [],
rows: [{
ceils: [],
rowIndex: 0,
scroll: 0
}],
rows: [
{
ceils: [],
rowIndex: 0,
scroll: 0
}
],
widths: [],
heights: [0],
avgHeight: 0,
@ -163,7 +188,7 @@ const calcData = () => {
animation(true)
}
onMounted(()=> {
onMounted(() => {
calcData()
})
@ -185,19 +210,21 @@ const calcHeaderData = () => {
const calcRowsData = () => {
let { dataset, index, headerBGC, rowNum } = status.mergedConfig
if (index) {
dataset = dataset.map((row:any, i:number) => {
dataset = dataset.map((row: any, i: number) => {
row = [...row]
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${i + 1}</span>`
const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${
i + 1
}</span>`
row.unshift(indexTag)
return row
})
}
dataset = dataset.map((ceils:any, i:number) => ({ ceils, rowIndex: i }))
dataset = dataset.map((ceils: any, i: number) => ({ ceils, rowIndex: i }))
const rowLength = dataset.length
if (rowLength > rowNum && rowLength < 2 * rowNum) {
dataset = [...dataset, ...dataset]
}
dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i }))
dataset = dataset.map((d: any, i: number) => ({ ...d, scroll: i }))
status.rowsData = dataset
status.rows = dataset
@ -206,7 +233,7 @@ const calcRowsData = () => {
const calcWidths = () => {
const { mergedConfig, rowsData } = status
const { columnWidth, header } = mergedConfig
const usedWidth = columnWidth.reduce((all:any, ws:number) => all + ws, 0)
const usedWidth = columnWidth.reduce((all: any, ws: number) => all + ws, 0)
let columnNum = 0
if (rowsData[0]) {
columnNum = (rowsData[0] as any).ceils.length
@ -254,7 +281,7 @@ const animation = async (start = false) => {
const rowLength = rowsData.length
if (rowNum >= rowLength) return
if (start) {
await new Promise(resolve => setTimeout(resolve, waitTime*1000))
await new Promise(resolve => setTimeout(resolve, waitTime * 1000))
if (updater !== status.updater) return
}
const animationNum = carousel === 'single' ? 1 : rowNum
@ -269,7 +296,7 @@ const animation = async (start = false) => {
const back = animationIndex - rowLength
if (back >= 0) animationIndex = back
status.animationIndex = animationIndex
status.animationHandler = setTimeout(animation, waitTime*1000 - 300) as any
status.animationHandler = setTimeout(animation, waitTime * 1000 - 300) as any
}
const stopAnimation = () => {
@ -279,9 +306,13 @@ const stopAnimation = () => {
}
const onRestart = async () => {
if (!status.mergedConfig) return
stopAnimation()
calcData()
try {
if (!status.mergedConfig) return
stopAnimation()
calcData()
} catch (error) {
console.log(error)
}
}
watch(
@ -304,7 +335,7 @@ watch(
() => {
onRestart()
},
{deep:true}
{ deep: true }
)
// ( dataset)
@ -316,7 +347,6 @@ useChartDataFetch(props.chartConfig, useChartEditStore, (resData: any[]) => {
onUnmounted(() => {
stopAnimation()
})
</script>
<style lang="scss" scoped>

View File

@ -493,34 +493,31 @@ export const useChartEditStore = defineStore({
}
const parseHandle = (e: CreateComponentType | CreateComponentGroupType) => {
e = cloneDeep(e)
// 生成新 id
e.id = getUUID()
e.attr.x = this.getMousePosition.x + 30
e.attr.y = this.getMousePosition.y + 30
// 外层生成新 id
e.id = getUUID()
// 分组列表生成新 id
if (e.isGroup) {
(e as CreateComponentGroupType).groupList.forEach((item: CreateComponentType) => {
item.id = getUUID()
})
}
return e
}
const isCut = recordCharts.type === HistoryActionTypeEnum.CUT
const targetList = Array.isArray(recordCharts.charts) ? recordCharts.charts : [ recordCharts.charts ]
// 多项
if (Array.isArray(recordCharts.charts)) {
recordCharts.charts.forEach((e: CreateComponentType) => {
this.addComponentList(parseHandle(e), undefined, true)
// 剪切需删除原数据
if (isCut) {
this.setTargetSelectChart(e.id)
this.removeComponentList(undefined, true)
}
})
if (isCut) this.setRecordChart(undefined)
loadingFinish()
return
}
// 单项
this.addComponentList(parseHandle(recordCharts.charts), undefined, true)
if (isCut) {
this.setTargetSelectChart(recordCharts.charts.id)
this.removeComponentList()
this.setRecordChart(undefined)
}
targetList.forEach((e: CreateComponentType | CreateComponentGroupType) => {
this.addComponentList(parseHandle(e), undefined, true)
// 剪切需删除原数据
if (isCut) {
this.setTargetSelectChart(e.id)
this.removeComponentList(undefined, true)
}
})
if (isCut) this.setRecordChart(undefined)
loadingFinish()
} catch (value) {
loadingError()

View File

@ -4,6 +4,6 @@ export * from '@/utils/router'
export * from '@/utils/storage'
export * from '@/utils/style'
export * from '@/utils/plugin'
export * from '@/utils/componets'
export * from '@/utils/components'
export * from '@/utils/type'
export * from '@/utils/file'

View File

@ -7,7 +7,9 @@ import html2canvas from 'html2canvas'
import { downloadByA } from './file'
import { toString } from './type'
import cloneDeep from 'lodash/cloneDeep'
import { WinKeyboard } from '@/enums/editPageEnum'
import { RequestHttpIntervalEnum, RequestParamsObjType } from '@/enums/httpEnum'
import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
/**
* *
@ -22,7 +24,7 @@ export const isDev = () => {
* @param { Number } randomLength
*/
export const getUUID = (randomLength = 10) => {
return Number(Math.random().toString().substr(2, randomLength) + Date.now()).toString(36)
return Number(Math.random().toString().substring(2, randomLength) + Date.now()).toString(36)
}
/**
@ -43,21 +45,8 @@ export const renderLang = (lang: string, set = {}, tag = 'span') => {
return () => h(tag, set, { default: () => window['$t'](lang) })
}
/**
* ! 使
* * vite 使 require utils
* @param path
* @param name
* @returns url
*/
// export const requireUrl = (path: string, name: string) => {
// return new URL(`${path}/${name}`, import.meta.url).href
// }
/**
* * 404
* @param path
* @param name
* @returns url
*/
export const requireErrorImg = () => {
@ -85,6 +74,21 @@ export const screenfullFn = (isFullscreen?: boolean, isEnabled?: boolean) => {
window['$message'].warning('您的浏览器不支持全屏功能!')
}
/**
*
* @param target
* @param x X轴
* @param y Y轴
*/
export const setComponentPosition = (
target: CreateComponentType | CreateComponentGroupType,
x?: number,
y?: number
) => {
x && (target.attr.x = x)
y && (target.attr.y = y)
}
/**
* *
* @param HTMLElement
@ -193,14 +197,16 @@ export const canvasCut = (html: HTMLElement | null, callback?: Function) => {
/**
* *
* @param data
* @param res
* @param funcStr
* @param toString
* @param isToString
* @param errorCallBack
* @param successCallBack
* @returns
*/
export const newFunctionHandle = (
data: any,
res: any,
funcStr?: string,
isToString?: boolean,
errorCallBack?: Function,
@ -208,8 +214,8 @@ export const newFunctionHandle = (
) => {
try {
if (!funcStr) return data
const fn = new Function('data', funcStr)
const fnRes = fn(cloneDeep(data))
const fn = new Function('data', 'res', funcStr)
const fnRes = fn(cloneDeep(data), cloneDeep(res))
const resHandle = isToString ? toString(fnRes) : fnRes
// 成功回调
successCallBack && successCallBack(resHandle)
@ -258,5 +264,24 @@ export const objToCookie = (obj: RequestParamsObjType) => {
for (const key in obj) {
str += key + '=' + obj[key] + ';'
}
return str.substr(0, str.length - 1)
return str.substring(0, str.length - 1)
}
/**
* *
* @param keyCode
* @returns
*/
export const setKeyboardDressShow = (keyCode?: number) => {
const code = new Map([[17, WinKeyboard.CTRL]])
const dom = document.getElementById('keyboard-dress-show')
if (!dom) return
if (!keyCode) {
dom.innerText = ''
return
}
if (keyCode && code.has(keyCode)) {
dom.innerText = `按下了「${code.get(keyCode)}」键`
}
}

View File

@ -127,8 +127,9 @@ const sendHandle = async () => {
try {
const res = await customizeHttp(toRaw(targetData.value.request), toRaw(chartEditStore.requestGlobalConfig))
loading.value = false
if (res && res.data) {
targetData.value.option.dataset = newFunctionHandle(res.data, targetData.value.filter)
if (res) {
if(!res?.data && !targetData.value.filter) window['$message'].warning('您的数据不符合默认格式,请配置过滤器!')
targetData.value.option.dataset = newFunctionHandle(res?.data, res, targetData.value.filter)
showMatching.value = true
return
}

View File

@ -27,7 +27,7 @@
</n-timeline-item>
<n-timeline-item v-show="filterShow" color="#97846c" :title="TimelineTitleEnum.FILTER">
<n-space :size="18" vertical>
<n-text depth="3">过滤器处理接口返回值的data字段</n-text>
<n-text depth="3">过滤器默认处理接口返回值的data字段</n-text>
<chart-data-monaco-editor></chart-data-monaco-editor>
</n-space>
</n-timeline-item>

View File

@ -1,7 +1,7 @@
<template>
<template v-if="targetData.filter">
<n-card>
<p><span class="func-keyword">function</span>&nbsp;&nbsp;filter(data)&nbsp;&nbsp;{</p>
<p><span class="func-keyword">function</span>&nbsp;&nbsp;filter(data, res)&nbsp;&nbsp;{</p>
<!-- 函数体 -->
<div class="go-ml-4">
<n-code :code="targetData.filter" language="typescript"></n-code>
@ -47,7 +47,7 @@
<div>
<n-space vertical>
<n-tag type="info">
<span class="func-keyword">function</span>&nbsp;&nbsp;filter(data)&nbsp;&nbsp;{
<span class="func-keyword">function</span>&nbsp;&nbsp;filter(data, res)&nbsp;&nbsp;{
</n-tag>
<monaco-editor v-model:modelValue="filter" width="460px" height="380px" language="javascript" />
<n-tag type="info">}</n-tag>
@ -58,14 +58,20 @@
<n-space :size="15" vertical>
<div class="editor-data-show">
<n-space>
<n-text depth="3">目标数据</n-text>
<n-code :code="toString(sourceData)" language="json" :word-wrap="true"></n-code>
<n-text depth="3">默认过滤数据(data)</n-text>
<n-code :code="toString(sourceData?.data) || '暂无'" language="json" :word-wrap="true"></n-code>
</n-space>
</div>
<div class="editor-data-show">
<n-space>
<n-text depth="3">接口返回数据(res)</n-text>
<n-code :code="toString(sourceData) || '暂无'" language="json" :word-wrap="true"></n-code>
</n-space>
</div>
<div class="editor-data-show">
<n-space>
<n-text depth="3">过滤器结果</n-text>
<n-code :code="filterRes" language="json" :word-wrap="true"></n-code>
<n-code :code="filterRes || '暂无'" language="json" :word-wrap="true"></n-code>
</n-space>
</div>
</n-space>
@ -81,7 +87,7 @@
</template>
规则
</n-tag>
<n-text class="go-ml-2" depth="2">过滤器处理接口返回值的data字段</n-text>
<n-text class="go-ml-2" depth="2">过滤器默认处理接口返回值的data字段</n-text>
</div>
<n-space>
@ -123,8 +129,8 @@ const sourceData = ref<any>('')
const fetchTargetData = async () => {
try {
const res = await customizeHttp(toRaw(targetData.value.request), toRaw(chartEditStore.requestGlobalConfig))
if (res && res.data) {
sourceData.value = res.data
if (res) {
sourceData.value = res
return
}
window['$message'].warning('数据异常,请检查参数!')
@ -136,8 +142,9 @@ const fetchTargetData = async () => {
//
const filterRes = computed(() => {
try {
const fn = new Function('data', filter.value)
const res = fn(cloneDeep(sourceData.value))
const fn = new Function('data', 'res', filter.value)
const response = cloneDeep(sourceData.value)
const res = fn(response?.data, response)
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
errorFlag.value = false
return toString(res)

View File

@ -76,7 +76,8 @@ import {
scatterBasicUrl,
mapUrl,
wordCloudUrl,
treemapUrl
treemapUrl,
threeEarth01Url
} from '@/api/mock'
const { HelpOutlineIcon } = icon.ionicons5
@ -127,6 +128,9 @@ const apiList = [
{
value: `【树图】${treemapUrl}`
},
{
value: `【三维地球】${threeEarth01Url}`
},
]
</script>

View File

@ -17,6 +17,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { useSettingStore } from '@/store/modules/settingStore/settingStore'
import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
import { setComponentPosition } from '@/utils'
import throttle from 'lodash/throttle'
import cloneDeep from 'lodash/cloneDeep'
//
@ -96,146 +97,150 @@ const canvasPositionList = computed(() => {
watch(
() => chartEditStore.getMousePosition,
throttle(() => {
if (!isComputedLine.value || selectId.value.length !== 1) return
//
try {
if (!isComputedLine.value || selectId.value.length !== 1) return
//
const selectW = selectAttr.value.w
const selectH = selectAttr.value.h
const selectW = selectAttr.value.w
const selectH = selectAttr.value.h
//
const selectLeftX = selectAttr.value.x
const selectHalfX = selectLeftX + selectW / 2
const selectRightX = selectLeftX + selectW
const seletX = [selectLeftX, selectHalfX, selectRightX]
//
const selectLeftX = selectAttr.value.x
const selectHalfX = selectLeftX + selectW / 2
const selectRightX = selectLeftX + selectW
const seletX = [selectLeftX, selectHalfX, selectRightX]
//
const selectTopY = selectAttr.value.y
const selectHalfY = selectTopY + selectH / 2
const selectBottomY = selectTopY + selectH
const selectY = [selectTopY, selectHalfY, selectBottomY]
//
const selectTopY = selectAttr.value.y
const selectHalfY = selectTopY + selectH / 2
const selectBottomY = selectTopY + selectH
const selectY = [selectTopY, selectHalfY, selectBottomY]
line.select.clear()
line.sorptioned.y = false
//
const componentList = chartEditStore.getComponentList.map((e: CreateComponentType | CreateComponentGroupType) => {
return {
id: e.id,
attr: e.attr
}
})
componentList.push(canvasPositionList.value)
//
line.lineArr.forEach(lineItem => {
componentList.forEach((component: typeof canvasPositionList.value) => {
//
if (selectId.value[0] === component.id) return
const componentW = component.attr.w
const componentH = component.attr.h
//
const componentLeftX = component.attr.x
const componentHalfX = componentLeftX + componentW / 2
const componentRightX = componentLeftX + componentW
const componentX = [componentLeftX, componentHalfX, componentRightX]
//
const componentTopY = component.attr.y
const componentHalfY = componentTopY + componentH / 2
const componentBottomY = componentTopY + componentH
const componentY = [componentTopY, componentHalfY, componentBottomY]
// 线 Y
if (lineItem.includes('rowt')) {
//
if (isSorption(selectTopY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
selectTarget.value.setPosition(selectLeftX, componentTopY)
}
if (isSorption(selectTopY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
selectTarget.value.setPosition(selectLeftX, componentHalfY)
}
if (isSorption(selectTopY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
selectTarget.value.setPosition(selectLeftX, componentBottomY)
}
}
if (lineItem.includes('rowc')) {
//
if (isSorption(selectHalfY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
selectTarget.value.setPosition(selectLeftX, componentTopY - selectH / 2)
}
if (isSorption(selectHalfY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
selectTarget.value.setPosition(selectLeftX, componentHalfY - selectH / 2)
}
if (isSorption(selectHalfY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
selectTarget.value.setPosition(selectLeftX, componentBottomY - selectH / 2)
}
}
if (lineItem.includes('rowb')) {
//
if (isSorption(selectBottomY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
selectTarget.value.setPosition(selectLeftX, componentTopY - selectH)
}
if (isSorption(selectBottomY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
selectTarget.value.setPosition(selectLeftX, componentHalfY - selectH)
}
if (isSorption(selectBottomY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
selectTarget.value.setPosition(selectLeftX, componentBottomY - selectH)
}
}
// 线 X
if (lineItem.includes('coll')) {
if (isSorption(selectLeftX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
selectTarget.value.setPosition(componentLeftX, selectTopY)
}
if (isSorption(selectLeftX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
selectTarget.value.setPosition(componentHalfX, selectTopY)
}
if (isSorption(selectLeftX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
selectTarget.value.setPosition(componentRightX, selectTopY)
}
}
if (lineItem.includes('colc')) {
if (isSorption(selectHalfX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
selectTarget.value.setPosition(componentLeftX - selectW / 2, selectTopY)
}
if (isSorption(selectHalfX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
selectTarget.value.setPosition(componentHalfX - selectW / 2, selectTopY)
}
if (isSorption(selectHalfX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
selectTarget.value.setPosition(componentRightX - selectW / 2, selectTopY)
}
}
if (lineItem.includes('colr')) {
if (isSorption(selectRightX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
selectTarget.value.setPosition(componentLeftX - selectW, selectTopY)
}
if (isSorption(selectRightX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
selectTarget.value.setPosition(componentHalfX - selectW, selectTopY)
}
if (isSorption(selectRightX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
selectTarget.value.setPosition(componentRightX - selectW, selectTopY)
}
line.select.clear()
line.sorptioned.y = false
//
const componentList = chartEditStore.getComponentList.map((e: CreateComponentType | CreateComponentGroupType) => {
return {
id: e.id,
attr: e.attr
}
})
})
componentList.push(canvasPositionList.value)
//
line.lineArr.forEach(lineItem => {
componentList.forEach((component: typeof canvasPositionList.value) => {
//
if (selectId.value[0] === component.id) return
const componentW = component.attr.w
const componentH = component.attr.h
//
const componentLeftX = component.attr.x
const componentHalfX = componentLeftX + componentW / 2
const componentRightX = componentLeftX + componentW
const componentX = [componentLeftX, componentHalfX, componentRightX]
//
const componentTopY = component.attr.y
const componentHalfY = componentTopY + componentH / 2
const componentBottomY = componentTopY + componentH
const componentY = [componentTopY, componentHalfY, componentBottomY]
// 线 Y
if (lineItem.includes('rowt')) {
//
if (isSorption(selectTopY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
setComponentPosition(selectTarget.value, selectLeftX, componentTopY)
}
if (isSorption(selectTopY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY)
}
if (isSorption(selectTopY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY)
}
}
if (lineItem.includes('rowc')) {
//
if (isSorption(selectHalfY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
setComponentPosition(selectTarget.value, selectLeftX, componentTopY - selectH / 2)
}
if (isSorption(selectHalfY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY - selectH / 2)
}
if (isSorption(selectHalfY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY - selectH / 2)
}
}
if (lineItem.includes('rowb')) {
//
if (isSorption(selectBottomY, componentTopY)) {
line.select.set(lineItem, { y: componentTopY })
setComponentPosition(selectTarget.value, selectLeftX, componentTopY - selectH)
}
if (isSorption(selectBottomY, componentHalfY)) {
line.select.set(lineItem, { y: componentHalfY })
setComponentPosition(selectTarget.value, selectLeftX, componentHalfY - selectH)
}
if (isSorption(selectBottomY, componentBottomY)) {
line.select.set(lineItem, { y: componentBottomY })
setComponentPosition(selectTarget.value, selectLeftX, componentBottomY - selectH)
}
}
// 线 X
if (lineItem.includes('coll')) {
if (isSorption(selectLeftX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
setComponentPosition(selectTarget.value, componentLeftX, selectTopY)
}
if (isSorption(selectLeftX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
setComponentPosition(selectTarget.value, componentHalfX, selectTopY)
}
if (isSorption(selectLeftX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
setComponentPosition(selectTarget.value, componentRightX, selectTopY)
}
}
if (lineItem.includes('colc')) {
if (isSorption(selectHalfX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
setComponentPosition(selectTarget.value, componentLeftX - selectW / 2, selectTopY)
}
if (isSorption(selectHalfX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
setComponentPosition(selectTarget.value, componentHalfX - selectW / 2, selectTopY)
}
if (isSorption(selectHalfX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
setComponentPosition(selectTarget.value, componentRightX - selectW / 2, selectTopY)
}
}
if (lineItem.includes('colr')) {
if (isSorption(selectRightX, componentLeftX)) {
line.select.set(lineItem, { x: componentLeftX })
setComponentPosition(selectTarget.value, componentLeftX - selectW, selectTopY)
}
if (isSorption(selectRightX, componentHalfX)) {
line.select.set(lineItem, { x: componentHalfX })
setComponentPosition(selectTarget.value, componentHalfX - selectW, selectTopY)
}
if (isSorption(selectRightX, componentRightX)) {
line.select.set(lineItem, { x: componentRightX })
setComponentPosition(selectTarget.value, componentRightX - selectW, selectTopY)
}
}
})
})
} catch (err) {
console.log(err)
}
}, 200),
{
deep: true

View File

@ -1,11 +1,16 @@
<template>
<div class="go-edit-bottom">
<edit-history></edit-history>
<n-space>
<!-- 历史记录 -->
<edit-history></edit-history>
<!-- CTRL按键触发展示 -->
<n-text id="keyboard-dress-show" depth="3"></n-text>
</n-space>
<n-space class="bottom-ri">
<!-- 快捷键提示 -->
<edit-shortcut-key />
<!-- 缩放比例 -->
<n-select
:disabled="lockScale"
@ -14,18 +19,13 @@
size="mini"
:options="filterOptions"
@update:value="selectHandle"
></n-select>
></n-select>
<!-- 锁定缩放 -->
<n-tooltip trigger="hover">
<template #trigger>
<n-button @click="lockHandle" text>
<n-icon
class="lock-icon"
:class="{ color: lockScale }"
size="18"
:depth="2"
>
<n-icon class="lock-icon" :class="{ color: lockScale }" size="18" :depth="2">
<lock-closed-outline-icon v-if="lockScale"></lock-closed-outline-icon>
<lock-open-outline-icon v-else></lock-open-outline-icon>
</n-icon>
@ -46,7 +46,7 @@
:disabled="lockScale"
:marks="sliderMaks"
@update:value="sliderHandle"
></n-slider>
></n-slider>
</n-space>
</div>
</template>

View File

@ -33,15 +33,23 @@ export const useFile = () => {
negativeButtonProps: { type: 'info', ghost: false },
// 新增
onPositiveCallback: async () => {
fileData = JSON.parse(fileData)
await updateComponent(fileData, false, true)
window['$message'].success('导入成功!')
try {
fileData = JSON.parse(fileData)
await updateComponent(fileData, false, true)
window['$message'].success('导入成功!')
} catch (error) {
window['$message'].error('组件导入失败,请检查文件完整性!')
}
},
// 覆盖
onNegativeCallback: async () => {
fileData = JSON.parse(fileData)
await updateComponent(fileData, true, true)
window['$message'].success('导入成功!')
try {
fileData = JSON.parse(fileData)
await updateComponent(fileData, true, true)
window['$message'].success('导入成功!')
} catch (error) {
window['$message'].error('组件导入失败,请检查文件完整性!')
}
}
})
})

View File

@ -1,12 +1,12 @@
import { toRaw } from 'vue'
import { DragKeyEnum, MouseEventButton, WinKeyboard, MacKeyboard } from '@/enums/editPageEnum'
import { DragKeyEnum, MouseEventButton } from '@/enums/editPageEnum'
import { createComponent } from '@/packages'
import { ConfigType } from '@/packages/index.d'
import { CreateComponentType, CreateComponentGroupType, PickCreateComponentType } from '@/packages/index.d'
import { useContextMenu } from '@/views/chart/hooks/useContextMenu.hook'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { EditCanvasTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
import { loadingStart, loadingFinish, loadingError } from '@/utils'
import { loadingStart, loadingFinish, loadingError, setComponentPosition } from '@/utils'
import { throttle, cloneDeep } from 'lodash'
const chartEditStore = useChartEditStore()
@ -33,7 +33,7 @@ export const dragHandle = async (e: DragEvent) => {
// 创建新图表组件
let newComponent: CreateComponentType = await createComponent(dropData)
newComponent.setPosition(e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
setComponentPosition(newComponent, e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
chartEditStore.addComponentList(newComponent, false, true)
chartEditStore.setTargetSelectChart(newComponent.id)
loadingFinish()
@ -190,8 +190,7 @@ export const useMouseHandle = () => {
if (item.status.lock) return
onClickOutSide()
// 按下左键 + CTRL
if (e.buttons === MouseEventButton.LEFT && window.$KeyboardActive?.ctrl)
return
if (e.buttons === MouseEventButton.LEFT && window.$KeyboardActive?.ctrl) return
// 按下右键 + 选中多个 + 目标元素是多选子元素
const selectId = chartEditStore.getTargetChart.selectId
@ -264,38 +263,43 @@ export const useMouseHandle = () => {
// 基于右下角位置检测
currX = currX > canvasWidth - distance ? canvasWidth - distance : currX
currY = currY > canvasHeight - distance ? canvasHeight - distance : currY
componentInstance.attr = Object.assign(componentInstance.attr, {
x: currX,
y: currY
})
if (componentInstance) {
componentInstance.attr = Object.assign(componentInstance.attr, {
x: currX,
y: currY
})
}
})
return
}, 20)
const mouseup = () => {
chartEditStore.setMousePosition(0, 0, 0, 0)
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, false)
// 加入历史栈
if (prevComponentInstance.length) {
chartEditStore.getTargetChart.selectId.forEach(id => {
if (!targetMap.has(id)) return
const index = chartEditStore.fetchTargetIndex(id)
const curComponentInstance = chartEditStore.getComponentList[index]
// 找到记录的所选组件
prevComponentInstance.forEach(preItem => {
if (preItem.id === id) {
preItem.attr = Object.assign(preItem.attr, {
offsetX: curComponentInstance.attr.x - preItem.attr.x,
offsetY: curComponentInstance.attr.y - preItem.attr.y
})
}
try {
chartEditStore.setMousePosition(0, 0, 0, 0)
chartEditStore.setEditCanvas(EditCanvasTypeEnum.IS_DRAG, false)
// 加入历史栈
if (prevComponentInstance.length) {
chartEditStore.getTargetChart.selectId.forEach(id => {
if (!targetMap.has(id)) return
const index = chartEditStore.fetchTargetIndex(id)
const curComponentInstance = chartEditStore.getComponentList[index]
// 找到记录的所选组件
prevComponentInstance.forEach(preItem => {
if (preItem.id === id) {
preItem.attr = Object.assign(preItem.attr, {
offsetX: curComponentInstance.attr.x - preItem.attr.x,
offsetY: curComponentInstance.attr.y - preItem.attr.y
})
}
})
})
})
chartEditStore.moveComponentList(prevComponentInstance)
chartEditStore.moveComponentList(prevComponentInstance)
}
document.removeEventListener('mousemove', mousemove)
document.removeEventListener('mouseup', mouseup)
} catch (err) {
console.log(err)
}
document.removeEventListener('mousemove', mousemove)
document.removeEventListener('mouseup', mouseup)
}
document.addEventListener('mousemove', mousemove)

View File

@ -9,9 +9,9 @@
:fallback-src="requireErrorImg()"
></n-image>
<n-ellipsis style="margin-right: auto">
<n-text class="list-text" :depth="2">
<span class="list-text">
{{ props.componentData.chartConfig.title }}
</n-text>
</span>
</n-ellipsis>
<layers-status :isGroup="isGroup" :hover="hover" :status="status"></layers-status>
</div>

View File

@ -2,8 +2,9 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { WinKeyboard, MacKeyboard, MenuEnum } from '@/enums/editPageEnum'
import throttle from 'lodash/throttle'
import debounce from 'lodash/debounce'
import keymaster from 'keymaster'
import { setKeyboardDressShow } from '@/utils'
// Keymaster可以支持识别以下组合键shiftoptionaltctrlcontrolcommand和⌘
const chartEditStore = useChartEditStore()
@ -27,7 +28,7 @@ export const winKeyboardValue = {
[MenuEnum.LOCK]: winCtrlMerge('l'),
[MenuEnum.UNLOCK]: winCtrlMerge(winShiftMerge('l')),
[MenuEnum.HIDE]: winCtrlMerge('h'),
[MenuEnum.SHOW]: winCtrlMerge(winShiftMerge('h')),
[MenuEnum.SHOW]: winCtrlMerge(winShiftMerge('h'))
}
// 这个 Ctrl 后面还是换成了 ⌘
@ -52,7 +53,7 @@ export const macKeyboardValue = {
[MenuEnum.LOCK]: macCtrlMerge('l'),
[MenuEnum.UNLOCK]: macCtrlMerge(macShiftMerge('l')),
[MenuEnum.HIDE]: macCtrlMerge('h'),
[MenuEnum.SHOW]: macCtrlMerge(macShiftMerge('h')),
[MenuEnum.SHOW]: macCtrlMerge(macShiftMerge('h'))
}
// Win 快捷键列表
@ -77,7 +78,7 @@ const winKeyList: Array<string> = [
winKeyboardValue.unLock,
winKeyboardValue.hide,
winKeyboardValue.show,
winKeyboardValue.show
]
// Mac 快捷键列表
@ -102,7 +103,7 @@ const macKeyList: Array<string> = [
macKeyboardValue.unLock,
macKeyboardValue.hide,
macKeyboardValue.show,
macKeyboardValue.show
]
// 处理键盘记录
@ -113,11 +114,18 @@ const keyRecordHandle = () => {
}
document.onkeydown = (e: KeyboardEvent) => {
if(e.keyCode === 17 && window.$KeyboardActive) window.$KeyboardActive.ctrl = true
if(e.keyCode === 17 && window.$KeyboardActive) {
setKeyboardDressShow(e.keyCode)
window.$KeyboardActive.ctrl = true
}
}
document.onkeyup = (e: KeyboardEvent) => {
if(e.keyCode === 17 && window.$KeyboardActive) window.$KeyboardActive.ctrl = false
if(e.keyCode === 17 && window.$KeyboardActive)
{
window.$KeyboardActive.ctrl = false
setKeyboardDressShow()
}
}
}

View File

@ -18,7 +18,7 @@ const componentMerge = (object: any, sources: any, notComponent = false) => {
if (notComponent) return merge(object, sources)
// 组件排除 options
const option = sources.option
if(!option) return merge(object, sources)
if (!option) return merge(object, sources)
// 为 undefined 的 sources 来源对象属性将被跳过详见 https://www.lodashjs.com/docs/lodash.merge
sources.option = undefined
@ -66,37 +66,38 @@ export const useSync = () => {
intComponent(e as CreateComponentType)
}
})
// 创建函数-重新创建是为了处理类种方法消失的问题
const create = async (
_componentInstance: CreateComponentType,
callBack?: (componentInstance: CreateComponentType) => void
) => {
// 补充 class 上的方法
let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig)
if (callBack) {
if (changeId) {
callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() }))
} else {
callBack(componentMerge(newComponent, _componentInstance))
}
} else {
if (changeId) {
chartEditStore.addComponentList(
componentMerge(newComponent, { ..._componentInstance, id: getUUID() }),
false,
true
)
} else {
chartEditStore.addComponentList(componentMerge(newComponent, _componentInstance), false, true)
}
}
}
// 数据赋值
for (const key in projectData) {
// 组件
if (key === ChartEditStoreEnum.COMPONENT_LIST) {
for (const comItem of projectData[key]) {
// 重新创建是为了处理类种方法消失的问题
const create = async (
_componentInstance: CreateComponentType,
callBack?: (componentInstance: CreateComponentType) => void
) => {
// 补充 class 上的方法
let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig)
if (callBack) {
if (changeId) {
callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() }))
} else {
callBack(componentMerge(newComponent, _componentInstance))
}
} else {
if (changeId) {
chartEditStore.addComponentList(
componentMerge(newComponent, { ..._componentInstance, id: getUUID() }),
false,
true
)
} else {
chartEditStore.addComponentList(componentMerge(newComponent, _componentInstance), false, true)
}
}
}
if (comItem.isGroup) {
// 创建分组
let groupClass = new PublicGroupConfigClass()
@ -106,19 +107,19 @@ export const useSync = () => {
groupClass = componentMerge(groupClass, comItem)
}
// 注册子应用
// 异步注册子应用
const targetList: CreateComponentType[] = []
;(comItem as CreateComponentGroupType).groupList.forEach(groupItem => {
create(groupItem, e => {
for (const groupItem of (comItem as CreateComponentGroupType).groupList) {
await create(groupItem, e => {
targetList.push(e)
})
})
}
groupClass.groupList = targetList
// 分组插入到列表
chartEditStore.addComponentList(groupClass, false, true)
} else {
create(comItem as CreateComponentType)
await create(comItem as CreateComponentType)
}
}
} else {