Merge branch 'dev'

This commit is contained in:
奔跑的面条 2023-04-02 18:00:55 +08:00
commit a2f32b0289
37 changed files with 4605 additions and 1340 deletions

View File

@ -75,7 +75,7 @@
"sass": "^1.49.11", "sass": "^1.49.11",
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"typescript": "4.6.3", "typescript": "4.6.3",
"vite": "2.9.9", "vite": "4.2.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-importer": "^0.2.5", "vite-plugin-importer": "^0.2.5",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",

File diff suppressed because it is too large Load Diff

100
src/api/mock/graph.json Normal file
View File

@ -0,0 +1,100 @@
{
"nodes": [
{
"id": "0",
"name": "Myriel",
"symbolSize": "@integer(0, 50)",
"x": -266.82776,
"y": 299.6904,
"value": "@integer(0, 50)",
"category": 3
},
{
"id": "1",
"name": "Napoleon",
"symbolSize": "@integer(0, 50)",
"x": -418.08344,
"y": 446.8853,
"value": "@integer(0, 50)",
"category": 5
},
{
"id": "2",
"name": "MlleBaptistine",
"symbolSize": "@integer(0, 50)",
"x": -212.76357,
"y": 245.29176,
"value": "@integer(0, 50)",
"category": 1
},
{
"id": "3",
"name": "MmeMagloire",
"symbolSize": "@integer(0, 50)",
"x": -242.82404,
"y": 235.26283,
"value": "@integer(0, 50)",
"category": 1
},
{
"id": "4",
"name": "CountessDeLo",
"symbolSize": "@integer(0, 50)",
"x": -379.30386,
"y": 429.06424,
"value": "@integer(0, 50)",
"category": 0
}
],
"links": [
{
"source": "1",
"target": "@integer(2, 4)"
},
{
"source": "2",
"target": "@integer(3, 4)"
},
{
"source": "3",
"target": "@integer(0, 2)"
},
{
"source": "3",
"target": "@integer(0, 1)"
},
{
"source": "4",
"target": "@integer(0, 3)"
}
],
"categories": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
},
{
"name": "D"
},
{
"name": "E"
},
{
"name": "F"
},
{
"name": "G"
},
{
"name": "H"
},
{
"name": "I"
}
]
}

View File

@ -19,6 +19,8 @@ export const capsuleUrl = '/mock/capsule'
export const wordCloudUrl = '/mock/wordCloud' export const wordCloudUrl = '/mock/wordCloud'
export const treemapUrl = '/mock/treemap' export const treemapUrl = '/mock/treemap'
export const threeEarth01Url = '/mock/threeEarth01Data' export const threeEarth01Url = '/mock/threeEarth01Data'
export const sankeyUrl = '/mock/sankey'
export const graphUrl = '/mock/graphData'
const mockObject: MockMethod[] = [ const mockObject: MockMethod[] = [
{ {
@ -103,6 +105,16 @@ const mockObject: MockMethod[] = [
method: RequestHttpEnum.GET, method: RequestHttpEnum.GET,
response: () => test.threeEarth01Data response: () => test.threeEarth01Data
}, },
{
url: sankeyUrl,
method: RequestHttpEnum.GET,
response: () => test.fetchSankey
},
{
url: graphUrl,
method: RequestHttpEnum.GET,
response: () => test.graphData
},
] ]
export default mockObject export default mockObject

86
src/api/mock/sankey.json Normal file
View File

@ -0,0 +1,86 @@
{
"label": [
{
"name": "a"
},
{
"name": "b"
},
{
"name": "a1"
},
{
"name": "a2"
},
{
"name": "b1"
},
{
"name": "b2"
}
],
"links": [
{
"source": "a",
"target": "a1",
"value": "@integer(0, 10)"
},
{
"source": "a",
"target": "a2",
"value": "@integer(0, 10)"
},
{
"source": "b",
"target": "b1",
"value": "@integer(0, 10)"
},
{
"source": "a",
"target": "b1",
"value": "@integer(0, 10)"
},
{
"source": "b1",
"target": "a1",
"value": "@integer(0, 10)"
},
{
"source": "b1",
"target": "b2",
"value": "@integer(0, 10)"
}
],
"levels": [
{
"depth": 0,
"itemStyle": {
"color": "#decbe4"
},
"lineStyle": {
"color": "source",
"opacity": 0.9
}
},
{
"depth": 1,
"itemStyle": {
"color": "#b3cde3"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
},
{
"depth": 2,
"itemStyle": {
"color": "#ccebc5"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
}
]
}

View File

@ -2,6 +2,8 @@ import heatmapJson from './heatMapData.json'
import scatterJson from './scatter.json' import scatterJson from './scatter.json'
import mapJson from './map.json' import mapJson from './map.json'
import tTreemapJson from './treemap.json' import tTreemapJson from './treemap.json'
import sankeyJson from './sankey.json'
import graphDataJson from './graph.json'
export default { export default {
// 单图表 // 单图表
@ -219,5 +221,19 @@ export default {
'endArray|10': [{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }] 'endArray|10': [{ name: '@name', N: '@integer(10, 100)', E: '@integer(10, 100)' }]
} }
] ]
} },
// 桑基图
fetchSankey: {
code: 0,
status: 200,
msg: '请求成功',
data: sankeyJson
},
// 关系图
graphData: {
code: 0,
status: 200,
msg: '请求成功',
data: graphDataJson
},
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -12,6 +12,7 @@ export enum DragKeyEnum {
// 不同页面保存操作 // 不同页面保存操作
export enum SavePageEnum { export enum SavePageEnum {
CHART = 'SaveChart', CHART = 'SaveChart',
CHART_TO_PREVIEW = 'ChartToPreview',
JSON = 'SaveJSON', JSON = 'SaveJSON',
CLOSE = 'close' CLOSE = 'close'
} }

View File

@ -1,13 +1,9 @@
<template> <template>
<router-view> <router-view>
<template #default="{ Component, route }"> <template #default="{ Component, route }">
<component <component v-if="route.meta.noKeepAlive" :is="Component"></component>
v-if="route.noKeepAlive"
:is="Component"
:key="route.fullPath"
></component>
<keep-alive v-else> <keep-alive v-else>
<component :is="Component" :key="route.fullPath"></component> <component :is="Component"></component>
</keep-alive> </keep-alive>
</template> </template>
</router-view> </router-view>

View File

@ -1,14 +1,14 @@
<template> <template>
<router-view #default="{ Component, route }"> <router-view #default="{ Component, route }">
<transition name="fade" mode="out-in" appear> <transition name="fade" mode="out-in" appear>
<component <component
v-if="route.noKeepAlive" v-if="route.meta.noKeepAlive"
:is="Component" :is="Component"
:key="route.fullPath" :key="route.fullPath"
></component> ></component>
<keep-alive v-else> <keep-alive v-else>
<component :is="Component" :key="route.fullPath"></component> <component :is="Component" :key="route.fullPath"></component>
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
</template> </template>

View File

@ -1,15 +1,8 @@
<template> <template>
<v-chart <v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()"
ref="vChartRef"
:init-options="initOptions"
:theme="themeColor"
:option="option"
:manual-update="isPreview()"
:update-options="{ :update-options="{
replaceMerge: replaceMergeArr replaceMerge: replaceMergeArr
}" }" autoresize></v-chart>
autoresize
></v-chart>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -27,6 +20,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { isPreview } from '@/utils' import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import isObject from 'lodash/isObject' import isObject from 'lodash/isObject'
import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({ const props = defineProps({
themeSetting: { themeSetting: {
@ -61,11 +55,20 @@ watch(
if (!isObject(newData) || !('dimensions' in newData)) return if (!isObject(newData) || !('dimensions' in newData)) return
if (Array.isArray(newData?.dimensions)) { if (Array.isArray(newData?.dimensions)) {
const seriesArr = [] const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) { // oldData
seriesArr.push(seriesItem) // dimensionsYdimensions.length011X
const oldDimensions = Array.isArray(oldData?.dimensions)&&oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1;
const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1;
const dimensionsGap = newDimensions - oldDimensions;
if (dimensionsGap < 0) {
props.chartConfig.option.series.splice(newDimensions - 1)
} else if (dimensionsGap > 0) {
for (let i = 0; i < dimensionsGap; i++) {
seriesArr.push(cloneDeep(seriesItem))
}
props.chartConfig.option.series.push(...seriesArr)
} }
replaceMergeArr.value = ['series'] replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr
nextTick(() => { nextTick(() => {
replaceMergeArr.value = [] replaceMergeArr.value = []
}) })

View File

@ -26,6 +26,7 @@ import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore
import { isPreview } from '@/utils' import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components' import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
import isObject from 'lodash/isObject' import isObject from 'lodash/isObject'
import cloneDeep from 'lodash/cloneDeep'
const props = defineProps({ const props = defineProps({
themeSetting: { themeSetting: {
@ -61,7 +62,7 @@ watch(
if (Array.isArray(newData?.dimensions)) { if (Array.isArray(newData?.dimensions)) {
const seriesArr = [] const seriesArr = []
for (let i = 0; i < newData.dimensions.length - 1; i++) { for (let i = 0; i < newData.dimensions.length - 1; i++) {
seriesArr.push(seriesItem) seriesArr.push(cloneDeep(seriesItem))
} }
replaceMergeArr.value = ['series'] replaceMergeArr.value = ['series']
props.chartConfig.option.series = seriesArr props.chartConfig.option.series = seriesArr

View File

@ -0,0 +1,91 @@
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
import { DialConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import cloneDeep from 'lodash/cloneDeep'
export const includes = []
const option = {
backgroundColor: '#0E1327',
dataset:70,
series: [{
type: "gauge",
data: [{
value: 70,
itemStyle: { // 指针样式
color: '#2AF4FF'
}
}],
min: 0, //最小刻度
max: 100, //最大刻度
splitNumber: 10, //刻度数量
center: ['50%', '55%'],
radius: '80%',
axisLine: {
lineStyle: {
color: [
[0, 'rgba(0,212,230,0.5)'],
[1, 'rgba(28,128,245,0)']
],
width: 170
}
},
axisLabel: { // 文字样式
color: '#eee',
fontSize: 14,
},
axisTick: {
show: false,
},
splitLine: {
show: false,
},
detail: {
show: false,
},
pointer: {
length: '80%',
width: 4
},
animationDuration: 2000,
},
{
name: '外部刻度',
type: 'gauge',
center: ['50%', '55%'],
radius: '90%',
axisLine: {
show: true,
lineStyle: {
width: 25,
color: [ // 表盘外部颜色
[0, '#1369E380'],
[1, '#1369E380']
],
}
},
axisLabel: {
show:false,
},
axisTick: {
splitNumber: 5,
lineStyle: { //刻度颜色
color: '#42E5FB',
width: 2,
},
},
splitLine: {
length: 15,
lineStyle: {
color: '#42E5FB',
}
},
},
]
};
export default class Config extends PublicConfigClass implements CreateComponentType {
public key: string = DialConfig.key
public chartConfig = cloneDeep(DialConfig)
// 图表配置项
public option = echartOptionProfixHandle(option, includes)
}

View File

@ -0,0 +1,84 @@
<template>
<!-- 遍历 seriesList -->
<CollapseItem :name="`圆环`" :expanded="true">
<SettingItemBox name="数据">
<SettingItem name="数值">
<n-input-number v-model:value="config.dataset" :min="dialConfig.min" :max="dialConfig.max" :step="1" size="small" placeholder="数值">
</n-input-number>
</SettingItem>
</SettingItemBox>
<!-- Echarts 全局设置 -->
<!-- 表盘刻度字体 -->
<SettingItemBox name="字体">
<SettingItem name="颜色">
<n-color-picker size="small" :modes="['hex']" v-model:value="dialConfig.axisLabel.color"></n-color-picker>
</SettingItem>
<SettingItem name="字体大小">
<n-input-number
v-model:value="dialConfig.axisLabel.fontSize"
:min="0"
:step="1"
size="small"
placeholder="字体大小"
>
</n-input-number>
</SettingItem>
</SettingItemBox>
<!-- 表盘 -->
<SettingItemBox name="表盘外部">
<SettingItem name="颜色" >
<n-color-picker size="small" :modes="['hex']" v-model:value="config.series[1].axisLine.lineStyle.color[1][1]"></n-color-picker>
</SettingItem>
</SettingItemBox>
<!-- 指针 -->
<SettingItemBox name="指针">
<SettingItem name="颜色" >
<n-color-picker size="small" :modes="['hex']" v-model:value="dialConfig.data[0].itemStyle.color"></n-color-picker>
</SettingItem>
<SettingItem name="宽度">
<n-input-number v-model:value="dialConfig.pointer.width" :min="0" :step="1" size="small" placeholder="数值">
</n-input-number>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="刻度">
<SettingItem name="最小值">
<n-input-number v-model:value="dialConfig.min" :min="0" :step="1" size="small" placeholder="数值">
</n-input-number>
</SettingItem>
<SettingItem name="最大值">
<n-input-number v-model:value="dialConfig.max" :min="0" :step="1" size="small" placeholder="数值">
</n-input-number>
</SettingItem>
<SettingItem name="颜色" >
<n-color-picker size="small" :modes="['hex']" v-model:value="config.series[1].axisTick.lineStyle.color" @update:value="updateClick"></n-color-picker>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { GlobalThemeJsonType } from '@/settings/chartThemes'
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const config = computed(() => {
return props.optionData
})
const dialConfig = computed(() => {
return props.optionData.series[0]
})
const updateClick = (val: any) => {
props.optionData.series[1].splitLine.lineStyle.color=val
}
</script>

View File

@ -0,0 +1,14 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const DialConfig: ConfigType = {
key: 'Dial',
chartKey: 'VDial',
conKey: 'VCDial',
title: '表盘',
category: ChatCategoryEnum.MORE,
categoryName: ChatCategoryEnumName.MORE,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON,
image:'dial.png'
}

View File

@ -0,0 +1,69 @@
<template>
<v-chart :theme="themeColor" :init-options="initOptions" :option="option.value" autoresize> </v-chart>
</template>
<script setup lang="ts">
import { PropType, reactive, watch } from 'vue'
import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts'
import { mergeTheme } from '@/packages/public/chart'
import config, { includes } from './config'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<config>,
required: true
}
})
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
use([DatasetComponent, CanvasRenderer, PieChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent])
const option = reactive({
value: {}
})
const dataHandle = (newData: any) => {
let config = props.chartConfig.option
config.series[0].data[0].value = newData
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
option.value = props.chartConfig.option
}
//
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
dataHandle(newData)
} catch (error) {
console.log(error)
}
},
{
immediate: true,
deep: false
}
)
//
useChartDataFetch(props.chartConfig, useChartEditStore, (resData: number) => {
// @ts-ignore
option.value.series[0].data[0].value = resData
})
</script>

View File

@ -0,0 +1,71 @@
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
import { GraphConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = []
// 关系图布局
export const GraphLayout = [
{ label: '无', value: 'none' },
{ label: '环形', value: 'circular' }
]
// 标签开关
export const LabelSwitch = [
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 }
]
// 标签位置
export const LabelPosition = [
{ label: '左侧', value: 'left' },
{ label: '右侧', value: 'right' },
{ label: '顶部', value: 'top' },
{ label: '底部', value: 'bottom' },
{ label: '内部', value: 'inside' },
]
export const option = {
dataset: { ...dataJson },
tooltip: {},
legend:{
show:true,
textStyle:{
color:"#eee",
fontSize: 14 ,
},
data: dataJson.categories.map(function (a) {
return a.name;
})
},
series: [
{
type: 'graph',
layout: 'none', // none circular环形布局
data: dataJson.nodes,
links: dataJson.links,
categories: dataJson.categories,
label: { // 标签
show: 1,
position: 'right',
formatter: '{b}'
},
labelLayout: {
hideOverlap: true
},
lineStyle: {
color: 'source', // 线条颜色
curveness: 0.2 // 线条卷曲程度
}
}
]
};
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = GraphConfig.key
public chartConfig = cloneDeep(GraphConfig)
// 图表配置项
public option = echartOptionProfixHandle(option, includes)
}

View File

@ -0,0 +1,62 @@
<template>
<div>
<CollapseItem name="关系图" :expanded="true">
<SettingItemBox name="样式">
<setting-item name="布局">
<n-select v-model:value="graphConfig.layout" :options="GraphLayout" size="small" />
</setting-item>
</SettingItemBox>
<SettingItemBox name="标签">
<setting-item name="展示">
<n-select v-model:value="graphConfig.label.show" :options="LabelSwitch" size="small" />
</setting-item>
<setting-item name="位置">
<n-select v-model:value="graphConfig.label.position" :options="LabelPosition" size="small" />
</setting-item>
</SettingItemBox>
<SettingItemBox name="线条">
<SettingItem name="弧线">
<!-- 需要输入两位的小数才会变化 -->
<n-input-number
v-model:value="optionData.series[0].lineStyle.curveness"
:min="0"
:step="0.01"
placeholder="弯曲程度"
size="small"
></n-input-number>
</SettingItem>
</SettingItemBox>
<SettingItemBox name="图例">
<SettingItem name="颜色">
<n-color-picker
size="small"
:modes="['hex']"
v-model:value="optionData.legend.textStyle.color"
></n-color-picker>
</SettingItem>
<SettingItem name="文本">
<n-input-number v-model:value="optionData.legend.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小">
</n-input-number>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</div>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { option, GraphLayout, LabelSwitch, LabelPosition } from './config'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option & GlobalThemeJsonType>,
required: true
}
})
const graphConfig = computed<typeof option.series[0]>(() => {
return props.optionData.series[0]
})
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const GraphConfig: ConfigType = {
key: 'Graph',
chartKey: 'VGraph',
conKey: 'VCGraph',
title: '关系图',
category: ChatCategoryEnum.MORE,
categoryName: ChatCategoryEnumName.MORE,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON,
image: 'graph.png'
}

View File

@ -0,0 +1,80 @@
<template>
<v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" autoresize></v-chart>
</template>
<script setup lang="ts">
import { ref, computed, PropType, watch } from 'vue'
import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import dataJson from './data.json'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { RadarChart } from 'echarts/charts'
import { includes } from './config'
import { mergeTheme, setOption } from '@/packages/public/chart'
import { useChartDataFetch } from '@/hooks'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
use([DatasetComponent, CanvasRenderer, RadarChart, GridComponent, TooltipComponent, LegendComponent])
const vChartRef = ref<typeof VChart>()
const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
})
const dataSetHandle = (dataset: typeof dataJson) => {
if (dataset.nodes) {
props.chartConfig.option.series[0].data = dataset.nodes
}
if (dataset.links) {
props.chartConfig.option.series[0].links = dataset.links
}
if (dataset.categories) {
props.chartConfig.option.series[0].categories = dataset.categories
// @ts-ignore
props.chartConfig.option.legend.data = dataset.categories.map((i: { name: string }) => i.name)
}
if (vChartRef.value && isPreview()) {
setOption(vChartRef.value, props.chartConfig.option)
}
}
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
dataSetHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: false
}
)
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
dataSetHandle(newData)
})
</script>

View File

@ -0,0 +1,43 @@
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
import { SankeyConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
export const includes = ['legend']
// 图表方向
export const orientList = [
{ label: '水平', value: 'horizontal' },
{ label: '垂直', value: 'vertical' }
]
// 标签展示
export const toolTipSwitch = [
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 }
]
export const option = {
dataset: { ...dataJson },
tooltip: {
show: 1,
trigger: 'item',
triggerOn: 'mousemove'
},
series: {
type: 'sankey',
layout: 'none',
orient:'horizontal',
data: dataJson.label,
links: dataJson.links,
levels: dataJson.levels
}
};
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = SankeyConfig.key
public chartConfig = cloneDeep(SankeyConfig)
// 图表配置项
public option = echartOptionProfixHandle(option, includes)
}

View File

@ -0,0 +1,43 @@
<template>
<div>
<CollapseItem name="桑基图" :expanded="true">
<SettingItemBox name="样式">
<SettingItem name="方向">
<n-select
v-model:value="sankeyConfig.orient"
size="small"
:options="orientList"
placeholder="选择方向"
/>
</SettingItem>
<SettingItem name="提示标签">
<n-select
v-model:value="optionData.tooltip.show"
size="small"
:options="toolTipSwitch"
placeholder="是否开启"
/>
</SettingItem>
</SettingItemBox>
</CollapseItem>
</div>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { option, orientList, toolTipSwitch } from './config'
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
const props = defineProps({
optionData: {
type: Object as PropType<typeof option & GlobalThemeJsonType>,
required: true
}
})
const sankeyConfig = computed<typeof option.series>(() => {
return props.optionData.series
})
</script>

View File

@ -0,0 +1,86 @@
{
"label": [
{
"name": "a"
},
{
"name": "b"
},
{
"name": "a1"
},
{
"name": "a2"
},
{
"name": "b1"
},
{
"name": "b2"
}
],
"links": [
{
"source": "a",
"target": "a1",
"value": 5
},
{
"source": "a",
"target": "a2",
"value": 3
},
{
"source": "b",
"target": "b1",
"value": 8
},
{
"source": "a",
"target": "b1",
"value": 3
},
{
"source": "b1",
"target": "a1",
"value": 1
},
{
"source": "b1",
"target": "b2",
"value": 2
}
],
"levels": [
{
"depth": 0,
"itemStyle": {
"color": "#decbe4"
},
"lineStyle": {
"color": "source",
"opacity": 0.9
}
},
{
"depth": 1,
"itemStyle": {
"color": "#b3cde3"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
},
{
"depth": 2,
"itemStyle": {
"color": "#ccebc5"
},
"lineStyle": {
"color": "source",
"opacity": 0.6
}
}
]
}

View File

@ -0,0 +1,14 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
export const SankeyConfig: ConfigType = {
key: 'Sankey',
chartKey: 'VSankey',
conKey: 'VCSankey',
title: '桑基图',
category: ChatCategoryEnum.MORE,
categoryName: ChatCategoryEnumName.MORE,
package: PackagesCategoryEnum.CHARTS,
chartFrame: ChartFrameEnum.COMMON,
image: 'sankey.png'
}

View File

@ -0,0 +1,78 @@
<template>
<v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="option" :manual-update="isPreview()" autoresize></v-chart>
</template>
<script setup lang="ts">
import { ref, computed, PropType, watch } from 'vue'
import VChart from 'vue-echarts'
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
import dataJson from './data.json'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { RadarChart } from 'echarts/charts'
import { includes } from './config'
import { mergeTheme, setOption } from '@/packages/public/chart'
import { useChartDataFetch } from '@/hooks'
import { CreateComponentType } from '@/packages/index.d'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { isPreview } from '@/utils'
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
const props = defineProps({
themeSetting: {
type: Object,
required: true
},
themeColor: {
type: Object,
required: true
},
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
use([DatasetComponent, CanvasRenderer, RadarChart, GridComponent, TooltipComponent, LegendComponent])
const vChartRef = ref<typeof VChart>()
const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
})
const dataHandle = (dataset: typeof dataJson) => {
if (dataset.label) {
props.chartConfig.option.series.data = dataset.label
}
if (dataset.links) {
props.chartConfig.option.series.links = dataset.links
}
if (dataset.levels) {
props.chartConfig.option.series.levels = dataset.levels
}
if (vChartRef.value && isPreview()) {
setOption(vChartRef.value, props.chartConfig.option)
}
}
watch(
() => props.chartConfig.option.dataset,
newData => {
try {
dataHandle(newData)
} catch (error) {
console.log(error)
}
},
{
deep: true
}
)
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
dataHandle(newData)
})
</script>

View File

@ -4,5 +4,8 @@ import { FunnelConfig } from './Funnel/index'
import { HeatmapConfig } from './Heatmap/index' import { HeatmapConfig } from './Heatmap/index'
import { WaterPoloConfig } from './WaterPolo/index' import { WaterPoloConfig } from './WaterPolo/index'
import { TreeMapConfig } from './TreeMap/index' import { TreeMapConfig } from './TreeMap/index'
import { DialConfig } from './Dial/index'
import { SankeyConfig } from './Sankey/index'
import { GraphConfig } from './Graph/index'
export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig] export default [ProcessConfig, RadarConfig, FunnelConfig, HeatmapConfig, WaterPoloConfig, TreeMapConfig, GraphConfig, SankeyConfig, DialConfig]

View File

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

View File

@ -1,81 +1,87 @@
import { ChartList } from '@/packages/components/Charts/index' import { ChartList } from '@/packages/components/Charts/index'
import { DecorateList } from '@/packages/components/Decorates/index' import { DecorateList } from '@/packages/components/Decorates/index'
import { InformationList } from '@/packages/components/Informations/index' import { InformationList } from '@/packages/components/Informations/index'
import { TableList } from '@/packages/components/Tables/index' import { TableList } from '@/packages/components/Tables/index'
import { PackagesCategoryEnum, PackagesType, ConfigType, FetchComFlagType } from '@/packages/index.d' import { PackagesCategoryEnum, PackagesType, ConfigType, FetchComFlagType } from '@/packages/index.d'
const configModules = import.meta.globEager('./components/**/config.vue') const configModules: Record<string, { default: string }> = import.meta.glob('./components/**/config.vue', {
const indexModules = import.meta.globEager('./components/**/index.vue') eager: true
const imagesModules = import.meta.globEager('../assets/images/chart/**') })
const indexModules: Record<string, { default: string }> = import.meta.glob('./components/**/index.vue', {
// * 所有图表 eager: true
export let packagesList: PackagesType = { })
[PackagesCategoryEnum.CHARTS]: ChartList, const imagesModules: Record<string, { default: string }> = import.meta.glob('../assets/images/chart/**', {
[PackagesCategoryEnum.INFORMATIONS]: InformationList, eager: true
[PackagesCategoryEnum.TABLES]: TableList, })
[PackagesCategoryEnum.DECORATES]: DecorateList
} // * 所有图表
export let packagesList: PackagesType = {
/** [PackagesCategoryEnum.CHARTS]: ChartList,
* * [PackagesCategoryEnum.INFORMATIONS]: InformationList,
* @param targetData [PackagesCategoryEnum.TABLES]: TableList,
*/ [PackagesCategoryEnum.DECORATES]: DecorateList
export const createComponent = async (targetData: ConfigType) => { }
const { category, key } = targetData
const chart = await import(`./components/${targetData.package}/${category}/${key}/config.ts`) /**
return new chart.default() * *
} * @param targetData
*/
/** export const createComponent = async (targetData: ConfigType) => {
* * const { category, key } = targetData
* @param {string} chartName const chart = await import(`./components/${targetData.package}/${category}/${key}/config.ts`)
* @param {FetchComFlagType} flag 0, 1 return new chart.default()
*/ }
const fetchComponent = (chartName: string, flag: FetchComFlagType) => {
const module = flag === FetchComFlagType.VIEW ? indexModules : configModules /**
for (const key in module) { * *
const urlSplit = key.split('/') * @param {string} chartName
if (urlSplit[urlSplit.length - 2] === chartName) { * @param {FetchComFlagType} flag 0, 1
return module[key] */
} const fetchComponent = (chartName: string, flag: FetchComFlagType) => {
} const module = flag === FetchComFlagType.VIEW ? indexModules : configModules
} for (const key in module) {
const urlSplit = key.split('/')
/** if (urlSplit[urlSplit.length - 2] === chartName) {
* * return module[key]
* @param {ConfigType} dropData }
*/ }
export const fetchChartComponent = (dropData: ConfigType) => { }
const { key } = dropData
return fetchComponent(key, FetchComFlagType.VIEW)?.default /**
} * *
* @param {ConfigType} dropData
/** */
* * export const fetchChartComponent = (dropData: ConfigType) => {
* @param {ConfigType} dropData const { key } = dropData
*/ return fetchComponent(key, FetchComFlagType.VIEW)?.default
export const fetchConfigComponent = (dropData: ConfigType) => { }
const { key } = dropData
return fetchComponent(key, FetchComFlagType.CONFIG)?.default /**
} * *
* @param {ConfigType} dropData
/** */
* * export const fetchConfigComponent = (dropData: ConfigType) => {
* @param {ConfigType} targetData const { key } = dropData
*/ return fetchComponent(key, FetchComFlagType.CONFIG)?.default
export const fetchImages = async (targetData?: ConfigType) => { }
if (!targetData) return ''
// 新数据动态处理 /**
const { image, package: targetDataPackage } = targetData * *
// 兼容旧数据 * @param {ConfigType} targetData
if (image.includes('@') || image.includes('base64')) return image */
export const fetchImages = async (targetData?: ConfigType) => {
const imageName = image.substring(image.lastIndexOf('/') + 1) if (!targetData) return ''
for (const key in imagesModules) { // 新数据动态处理
const urlSplit = key.split('/') const { image, package: targetDataPackage } = targetData
if (urlSplit[urlSplit.length - 1] === imageName) { // 兼容旧数据
return imagesModules[key]?.default if (image.includes('@') || image.includes('base64')) return image
}
} const imageName = image.substring(image.lastIndexOf('/') + 1)
return '' for (const key in imagesModules) {
} const urlSplit = key.split('/')
if (urlSplit[urlSplit.length - 1] === imageName) {
return imagesModules[key]?.default
}
}
return ''
}

View File

@ -65,6 +65,7 @@ import { RequestHeader } from '../RequestHeader'
import { isDev } from '@/utils' import { isDev } from '@/utils'
import { icon } from '@/plugins' import { icon } from '@/plugins'
import { import {
graphUrl,
chartDataUrl, chartDataUrl,
chartSingleDataUrl, chartSingleDataUrl,
rankListUrl, rankListUrl,
@ -80,7 +81,8 @@ import {
capsuleUrl, capsuleUrl,
wordCloudUrl, wordCloudUrl,
treemapUrl, treemapUrl,
threeEarth01Url threeEarth01Url,
sankeyUrl
} from '@/api/mock' } from '@/api/mock'
const props = defineProps({ const props = defineProps({
@ -142,6 +144,12 @@ const apiList = [
}, },
{ {
value: `【三维地球】${threeEarth01Url}` value: `【三维地球】${threeEarth01Url}`
},
{
value: `【桑基图】${sankeyUrl}`
},
{
value: `【关系图】${graphUrl}`
} }
] ]
</script> </script>

View File

@ -24,6 +24,11 @@ export const syncData = () => {
}) })
} }
// 同步数据到预览页
export const syncDataToPreview = () => {
dispatchEvent(new CustomEvent(SavePageEnum.CHART_TO_PREVIEW, { detail: chartEditStore.getStorageInfo }))
}
// 侦听器更新 // 侦听器更新
const useSyncUpdateHandle = () => { const useSyncUpdateHandle = () => {
// 定义侦听器变量 // 定义侦听器变量
@ -48,8 +53,8 @@ const useSyncUpdateHandle = () => {
// document.hasFocus() && syncData() // document.hasFocus() && syncData()
// }, editToJsonInterval) // }, editToJsonInterval)
// 失焦同步数据(暂不开启) // 失焦同步数据
// addEventListener('blur', syncData) addEventListener('blur', syncDataToPreview)
// 监听编辑器保存事件 刷新工作台图表 // 监听编辑器保存事件 刷新工作台图表
addEventListener(SavePageEnum.JSON, updateFn) addEventListener(SavePageEnum.JSON, updateFn)
@ -61,7 +66,7 @@ const useSyncUpdateHandle = () => {
// 关闭侦听 // 关闭侦听
const unUse = () => { const unUse = () => {
// clearInterval(timer) // clearInterval(timer)
// removeEventListener('blur', syncData) removeEventListener('blur', syncDataToPreview)
removeEventListener(SavePageEnum.JSON, updateFn) removeEventListener(SavePageEnum.JSON, updateFn)
} }

View File

@ -322,6 +322,15 @@ $asideBottom: 70px;
border-radius: 25px; border-radius: 25px;
} }
} }
&::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 10px;
bottom: -10px;
cursor: pointer;
}
} }
/* 最小化 */ /* 最小化 */
&.isMini { &.isMini {
@ -348,6 +357,7 @@ $asideBottom: 70px;
50% { 50% {
opacity: 0; opacity: 0;
bottom: calc(#{$dockMiniBottom} - 10px); bottom: calc(#{$dockMiniBottom} - 10px);
pointer-events: none;
} }
100% { 100% {
opacity: 1; opacity: 1;
@ -362,15 +372,6 @@ $asideBottom: 70px;
display: none; display: none;
} }
} }
&::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 20px;
bottom: -20px;
cursor: pointer;
}
} }
} }
</style> </style>

View File

@ -11,7 +11,7 @@
<!-- 模块展示按钮 --> <!-- 模块展示按钮 -->
<n-tooltip v-for="item in btnList" :key="item.key" placement="bottom" trigger="hover"> <n-tooltip v-for="item in btnList" :key="item.key" placement="bottom" trigger="hover">
<template #trigger> <template #trigger>
<n-button size="small" ghost :type="styleHandle(item)" @click="clickHandle(item)"> <n-button size="small" ghost :type="styleHandle(item)" :focusable="false" @click="clickHandle(item)">
<component :is="item.icon"></component> <component :is="item.icon"></component>
</n-button> </n-button>
</template> </template>

View File

@ -1,25 +1,25 @@
<template> <template>
<preview :key="key"></preview> <preview :key="key"></preview>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getSessionStorageInfo } from './utils' import { getSessionStorageInfo } from './utils'
import type { ChartEditStorageType } from './index.d' import type { ChartEditStorageType } from './index.d'
import { SavePageEnum } from '@/enums/editPageEnum' import { SavePageEnum } from '@/enums/editPageEnum'
import { setSessionStorage } from '@/utils' import { setSessionStorage } from '@/utils'
import { StorageEnum } from '@/enums/storageEnum' import { StorageEnum } from '@/enums/storageEnum'
import { ref } from 'vue' import { ref } from 'vue'
import Preview from './index.vue' import Preview from './index.vue'
let key = ref(Date.now()) let key = ref(Date.now())
// -> // ->
;[SavePageEnum.JSON, SavePageEnum.CHART].forEach((saveEvent: string) => { ;[SavePageEnum.JSON, SavePageEnum.CHART_TO_PREVIEW].forEach((saveEvent: string) => {
if (!window.opener) return if (!window.opener) return
window.opener.addEventListener(saveEvent, async (e: any) => { window.opener.addEventListener(saveEvent, async (e: any) => {
const localStorageInfo: ChartEditStorageType = await getSessionStorageInfo() as unknown as ChartEditStorageType const localStorageInfo: ChartEditStorageType = await getSessionStorageInfo() as unknown as ChartEditStorageType
setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }]) setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }])
key.value = Date.now() key.value = Date.now()
}) })
}) })
</script> </script>

View File

@ -45,8 +45,7 @@ const collapsed = ref<boolean>(false)
const { getAsideCollapsedWidth } = toRefs(useSettingStore()) const { getAsideCollapsedWidth } = toRefs(useSettingStore())
const route = useRoute() const route = useRoute()
const routeRame = computed(() => route.name) const menuValue = computed(() => route.name)
const menuValue = ref(routeRame)
const menuOptions = menuOptionsInit() const menuOptions = menuOptionsInit()