Commit a4c0de0c871bc816600accd89f0a682cfd24e122

Authored by fengwotao
1 parent 4ee311d4

feat:新增动画组件和组合组件下的对应子项,大部分是复制原作者已有的,git追踪记录了,其他未做改动

Showing 53 changed files with 2558 additions and 0 deletions
  1 +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
  2 +import { WordCloudConfig } from './index'
  3 +import { CreateComponentType } from '@/packages/index.d'
  4 +import cloneDeep from 'lodash/cloneDeep'
  5 +import dataJson from './data.json'
  6 +
  7 +export const includes = []
  8 +
  9 +export const ShapeEnumList = [
  10 + { label: '圆形', value: 'circle' },
  11 + { label: '心形', value: 'cardioid' },
  12 + { label: '钻石', value: 'diamond' },
  13 + { label: '右三角形', value: 'triangle-forward' },
  14 + { label: '三角形', value: 'triangle' },
  15 + { label: '五边形', value: 'pentagon' },
  16 + { label: '星星', value: 'star' }
  17 +]
  18 +
  19 +export const option = {
  20 + dataset: [...dataJson],
  21 + tooltip: {},
  22 + series: [
  23 + {
  24 + type: 'wordCloud',
  25 +
  26 + // “云”绘制的形状,可以是表示为回调函数,也可以是固定关键字。
  27 + // 可用值有:circle|cardioid|diamond|triangle-forward|triangle|pentagon|star
  28 + shape: 'circle',
  29 +
  30 + // 白色区域将被排除在绘制文本之外的剪影图像。
  31 + // 随着云的形状生长,形状选项将继续应用。
  32 + // maskImage: maskImage,
  33 +
  34 + // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud
  35 + // Default to be put in the center and has 75% x 80% size.
  36 + left: 'center',
  37 + top: 'center',
  38 + width: '70%',
  39 + height: '80%',
  40 + right: null,
  41 + bottom: null,
  42 +
  43 + // 文本大小范围,默认 [12,60]
  44 + sizeRange: [12, 60],
  45 +
  46 + // 文本旋转范围和程度的步骤。 文本将通过旋转步骤45在[-90,90]中随机旋转
  47 + rotationRange: [0, 0],
  48 + rotationStep: 0,
  49 +
  50 + // size of the grid in pixels for marking the availability of the canvas
  51 + // 网格大小越大,单词之间的差距就越大。
  52 + gridSize: 8,
  53 +
  54 + // 设置为true,以允许单词在画布之外部分地绘制。允许绘制大于画布的大小
  55 + drawOutOfBound: false,
  56 +
  57 + // If perform layout animation.
  58 + // NOTE disable it will lead to UI blocking when there is lots of words.
  59 + layoutAnimation: true,
  60 +
  61 + // Global text style
  62 + textStyle: {
  63 + fontFamily: 'sans-serif',
  64 + fontWeight: 'bold'
  65 + // 颜色可以是回调功能或颜色字符串
  66 + // color: function () {
  67 + // // 随机颜色
  68 + // return (
  69 + // 'rgb(' +
  70 + // [Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160)].join(
  71 + // ','
  72 + // ) +
  73 + // ')'
  74 + // )
  75 + // }
  76 + },
  77 + emphasis: {
  78 + focus: 'self',
  79 +
  80 + textStyle: {
  81 + shadowBlur: 10,
  82 + shadowColor: '#333'
  83 + }
  84 + },
  85 + data: [...dataJson]
  86 + }
  87 + ]
  88 +}
  89 +
  90 +export default class Config extends PublicConfigClass implements CreateComponentType {
  91 + public key = WordCloudConfig.key
  92 + public chartConfig = cloneDeep(WordCloudConfig)
  93 + // 图表配置项
  94 + public option = echartOptionProfixHandle(option, includes)
  95 +}
... ...
  1 +<template>
  2 + <collapse-item name="词云" expanded>
  3 + <setting-item-box name="形状">
  4 + <setting-item>
  5 + <n-select v-model:value="optionData.series[0].shape" size="small" :options="ShapeEnumList" />
  6 + </setting-item>
  7 + <setting-item>
  8 + <n-checkbox v-model:checked="optionData.series[0].drawOutOfBound" size="small">允许出边</n-checkbox>
  9 + </setting-item>
  10 + </setting-item-box>
  11 +
  12 + <setting-item-box name="布局">
  13 + <setting-item name="宽度">
  14 + <n-slider
  15 + v-model:value="series.width"
  16 + :min="0"
  17 + :max="100"
  18 + :format-tooltip="sliderFormatTooltip"
  19 + @update:value="updateWidth"
  20 + ></n-slider>
  21 + </setting-item>
  22 + <setting-item name="高度">
  23 + <n-slider
  24 + v-model:value="series.height"
  25 + :min="0"
  26 + :max="100"
  27 + :format-tooltip="sliderFormatTooltip"
  28 + @update:value="updateHeight"
  29 + ></n-slider>
  30 + </setting-item>
  31 + </setting-item-box>
  32 +
  33 + <setting-item-box name="样式" alone>
  34 + <setting-item name="字体区间(最小/最大字体)">
  35 + <n-slider v-model:value="optionData.series[0].sizeRange" range :step="1" :min="6" :max="100" />
  36 + </setting-item>
  37 + <setting-item name="旋转角度">
  38 + <n-slider v-model:value="series.rotationStep" :step="15" :min="0" :max="45" @update:value="updateRotation" />
  39 + </setting-item>
  40 + </setting-item-box>
  41 + </collapse-item>
  42 +</template>
  43 +
  44 +<script setup lang="ts">
  45 +import { PropType, computed } from 'vue'
  46 +import { option, ShapeEnumList } from './config'
  47 +// eslint-disable-next-line no-unused-vars
  48 +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
  49 +
  50 +const props = defineProps({
  51 + optionData: {
  52 + type: Object as PropType<typeof option>,
  53 + required: true
  54 + }
  55 +})
  56 +
  57 +const series = computed(() => {
  58 + const { width, height, rotationStep } = props.optionData.series[0]
  59 + return {
  60 + width: +width.replace('%', ''),
  61 + height: +height.replace('%', ''),
  62 + rotationStep
  63 + }
  64 +})
  65 +
  66 +const sliderFormatTooltip = (v: number) => {
  67 + return `${v}%`
  68 +}
  69 +
  70 +const updateWidth = (value: number) => {
  71 + props.optionData.series[0].width = `${value}%`
  72 +}
  73 +
  74 +const updateHeight = (value: number) => {
  75 + props.optionData.series[0].height = `${value}%`
  76 +}
  77 +
  78 +const updateRotation = (value: number) => {
  79 + props.optionData.series[0].rotationStep = value
  80 + props.optionData.series[0].rotationRange = value === 0 ? [0, 0] : [-90, 90]
  81 +}
  82 +</script>
... ...
  1 +[
  2 + {
  3 + "name": "数据可视化",
  4 + "value": 8000,
  5 + "textStyle": {
  6 + "color": "#78fbb2"
  7 + },
  8 + "emphasis": {
  9 + "textStyle": {
  10 + "color": "red"
  11 + }
  12 + }
  13 + },
  14 + {
  15 + "name": "GO VIEW",
  16 + "value": 6181
  17 + },
  18 + {
  19 + "name": "低代码",
  20 + "value": 4386
  21 + },
  22 + {
  23 + "name": "Vue3",
  24 + "value": 4055
  25 + },
  26 + {
  27 + "name": "TypeScript4",
  28 + "value": 2467
  29 + },
  30 + {
  31 + "name": "Vite2",
  32 + "value": 2244
  33 + },
  34 + {
  35 + "name": "NaiveUI",
  36 + "value": 1898
  37 + },
  38 + {
  39 + "name": "ECharts5",
  40 + "value": 1484
  41 + },
  42 + {
  43 + "name": "Axios",
  44 + "value": 1112
  45 + },
  46 + {
  47 + "name": "Pinia2",
  48 + "value": 965
  49 + },
  50 + {
  51 + "name": "PlopJS",
  52 + "value": 847
  53 + },
  54 + {
  55 + "name": "sfc",
  56 + "value": 582
  57 + },
  58 + {
  59 + "name": "SCSS",
  60 + "value": 555
  61 + },
  62 + {
  63 + "name": "pnpm",
  64 + "value": 550
  65 + },
  66 + {
  67 + "name": "eslint",
  68 + "value": 462
  69 + },
  70 + {
  71 + "name": "json",
  72 + "value": 366
  73 + },
  74 + {
  75 + "name": "图表",
  76 + "value": 360
  77 + },
  78 + {
  79 + "name": "地图",
  80 + "value": 282
  81 + },
  82 + {
  83 + "name": "时钟",
  84 + "value": 273
  85 + },
  86 + {
  87 + "name": "标题",
  88 + "value": 265
  89 + }
  90 +]
... ...
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
  3 +
  4 +export const WordCloudConfig: ConfigType = {
  5 + key: 'WordCloud',
  6 + chartKey: 'VWordCloud',
  7 + conKey: 'VCWordCloud',
  8 + title: '词云',
  9 + category: ChatCategoryEnum.MORE,
  10 + categoryName: ChatCategoryEnumName.MORE,
  11 + package: PackagesCategoryEnum.INFORMATIONS,
  12 + chartFrame: ChartFrameEnum.COMMON,
  13 + image: 'words_cloud.png'
  14 +}
... ...
  1 +<template>
  2 + <v-chart
  3 + ref="vChartRef"
  4 + :theme="themeColor"
  5 + :option="option"
  6 + :manual-update="isPreview()"
  7 + :update-options="{ replaceMerge: replaceMergeArr }"
  8 + autoresize
  9 + ></v-chart>
  10 +</template>
  11 +
  12 +<script setup lang="ts">
  13 +import { ref, computed, watch, PropType } from 'vue'
  14 +import VChart from 'vue-echarts'
  15 +import 'echarts-wordcloud'
  16 +import { use } from 'echarts/core'
  17 +import { CanvasRenderer } from 'echarts/renderers'
  18 +import config, { includes } from './config'
  19 +import { mergeTheme, setOption } from '@/packages/public/chart'
  20 +import { useChartDataFetch } from '@/hooks'
  21 +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  22 +import { isPreview } from '@/utils'
  23 +import { GridComponent, TooltipComponent } from 'echarts/components'
  24 +import dataJson from './data.json'
  25 +
  26 +const props = defineProps({
  27 + themeSetting: {
  28 + type: Object,
  29 + required: true
  30 + },
  31 + themeColor: {
  32 + type: Object,
  33 + required: true
  34 + },
  35 + chartConfig: {
  36 + type: Object as PropType<config>,
  37 + required: true
  38 + }
  39 +})
  40 +
  41 +use([CanvasRenderer, GridComponent, TooltipComponent])
  42 +
  43 +const replaceMergeArr = ref<string[]>()
  44 +
  45 +const option = computed(() => {
  46 + return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
  47 +})
  48 +
  49 +const dataSetHandle = (dataset: typeof dataJson) => {
  50 + try {
  51 + dataset && (props.chartConfig.option.series[0].data = dataset)
  52 + vChartRef.value && isPreview() && setOption(vChartRef.value, props.chartConfig.option)
  53 + } catch (error) {
  54 + console.log(error)
  55 + }
  56 +}
  57 +
  58 +// dataset 无法变更条数的补丁
  59 +watch(
  60 + () => props.chartConfig.option.dataset,
  61 + newData => {
  62 + dataSetHandle(newData)
  63 + },
  64 + {
  65 + deep: false
  66 + }
  67 +)
  68 +
  69 +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
  70 + dataSetHandle(newData)
  71 +})
  72 +</script>
... ...
  1 +import {WordCloudConfig} from './WordCloud/index'
  2 +
  3 +export default [WordCloudConfig]
... ...
  1 +// eslint-disable-next-line @typescript-eslint/ban-ts-comment
  2 +// @ts-nocheck
  3 +import {
  4 + ArcCurve,
  5 + BufferAttribute,
  6 + BufferGeometry,
  7 + Color,
  8 + Line,
  9 + LineBasicMaterial,
  10 + Points,
  11 + PointsMaterial,
  12 + Quaternion,
  13 + Vector3
  14 +} from 'three'
  15 +import { lon2xyz } from './common'
  16 +
  17 +/*
  18 + * 绘制一条圆弧飞线
  19 + * 5个参数含义:( 飞线圆弧轨迹半径, 开始角度, 结束角度)
  20 + */
  21 +function createFlyLine(radius, startAngle, endAngle, color) {
  22 + const geometry = new BufferGeometry() //声明一个几何体对象BufferGeometry
  23 + // ArcCurve创建圆弧曲线
  24 + const arc = new ArcCurve(0, 0, radius, startAngle, endAngle, false)
  25 + //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
  26 + const pointsArr = arc.getSpacedPoints(100) //分段数80,返回81个顶点
  27 + geometry.setFromPoints(pointsArr) // setFromPoints方法从pointsArr中提取数据改变几何体的顶点属性vertices
  28 + // 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小
  29 + const percentArr = [] //attributes.percent的数据
  30 + for (let i = 0; i < pointsArr.length; i++) {
  31 + percentArr.push(i / pointsArr.length)
  32 + }
  33 + const percentAttribue = new BufferAttribute(new Float32Array(percentArr), 1)
  34 + // 通过顶点数据percent点模型从大到小变化,产生小蝌蚪形状飞线
  35 + geometry.attributes.percent = percentAttribue
  36 + // 批量计算所有顶点颜色数据
  37 + const colorArr = []
  38 + for (let i = 0; i < pointsArr.length; i++) {
  39 + const color1 = new Color(0xec8f43) //轨迹线颜色 青色
  40 + const color2 = new Color(0xf3ae76) //黄色
  41 + const color = color1.lerp(color2, i / pointsArr.length)
  42 + colorArr.push(color.r, color.g, color.b)
  43 + }
  44 + // 设置几何体顶点颜色数据
  45 + geometry.attributes.color = new BufferAttribute(new Float32Array(colorArr), 3)
  46 + const size = 1.3
  47 + // 点模型渲染几何体每个顶点
  48 + const material = new PointsMaterial({
  49 + size, //点大小
  50 + // vertexColors: VertexColors, //使用顶点颜色渲染
  51 + transparent: true,
  52 + depthWrite: false
  53 + })
  54 + // 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的)
  55 + material.onBeforeCompile = function (shader) {
  56 + // 顶点着色器中声明一个attribute变量:百分比
  57 + shader.vertexShader = shader.vertexShader.replace(
  58 + 'void main() {',
  59 + [
  60 + 'attribute float percent;', //顶点大小百分比变量,控制点渲染大小
  61 + 'void main() {'
  62 + ].join('\n') // .join()把数组元素合成字符串
  63 + )
  64 + // 调整点渲染大小计算方式
  65 + shader.vertexShader = shader.vertexShader.replace(
  66 + 'gl_PointSize = size;',
  67 + ['gl_PointSize = percent * size;'].join('\n') // .join()把数组元素合成字符串
  68 + )
  69 + }
  70 + const FlyLine = new Points(geometry, material)
  71 + material.color = new Color(color)
  72 + FlyLine.name = '飞行线'
  73 +
  74 + return FlyLine
  75 +}
  76 +
  77 +/**输入地球上任意两点的经纬度坐标,通过函数flyArc可以绘制一个飞线圆弧轨迹
  78 + * lon1,lat1:轨迹线起点经纬度坐标
  79 + * lon2,lat2:轨迹线结束点经纬度坐标
  80 + */
  81 +function flyArc(radius, lon1, lat1, lon2, lat2, options) {
  82 + const sphereCoord1 = lon2xyz(radius, lon1, lat1) //经纬度坐标转球面坐标
  83 + // startSphereCoord:轨迹线起点球面坐标
  84 + const startSphereCoord = new Vector3(sphereCoord1.x, sphereCoord1.y, sphereCoord1.z)
  85 + const sphereCoord2 = lon2xyz(radius, lon2, lat2)
  86 + // startSphereCoord:轨迹线结束点球面坐标
  87 + const endSphereCoord = new Vector3(sphereCoord2.x, sphereCoord2.y, sphereCoord2.z)
  88 +
  89 + //计算绘制圆弧需要的关于y轴对称的起点、结束点和旋转四元数
  90 + const startEndQua = _3Dto2D(startSphereCoord, endSphereCoord)
  91 + // 调用arcXOY函数绘制一条圆弧飞线轨迹
  92 + const arcline = arcXOY(radius, startEndQua.startPoint, startEndQua.endPoint, options)
  93 + arcline.quaternion.multiply(startEndQua.quaternion)
  94 + return arcline
  95 +}
  96 +/*
  97 + * 把3D球面上任意的两个飞线起点和结束点绕球心旋转到到XOY平面上,
  98 + * 同时保持关于y轴对称,借助旋转得到的新起点和新结束点绘制
  99 + * 一个圆弧,最后把绘制的圆弧反向旋转到原来的起点和结束点即可
  100 + */
  101 +function _3Dto2D(startSphere, endSphere) {
  102 + /*计算第一次旋转的四元数:表示从一个平面如何旋转到另一个平面*/
  103 + const origin = new Vector3(0, 0, 0) //球心坐标
  104 + const startDir = startSphere.clone().sub(origin) //飞线起点与球心构成方向向量
  105 + const endDir = endSphere.clone().sub(origin) //飞线结束点与球心构成方向向量
  106 + // dir1和dir2构成一个三角形,.cross()叉乘计算该三角形法线normal
  107 + const normal = startDir.clone().cross(endDir).normalize()
  108 + const xoyNormal = new Vector3(0, 0, 1) //XOY平面的法线
  109 + //.setFromUnitVectors()计算从normal向量旋转达到xoyNormal向量所需要的四元数
  110 + // quaternion表示把球面飞线旋转到XOY平面上需要的四元数
  111 + const quaternion3D_XOY = new Quaternion().setFromUnitVectors(normal, xoyNormal)
  112 + /*第一次旋转:飞线起点、结束点从3D空间第一次旋转到XOY平面*/
  113 + const startSphereXOY = startSphere.clone().applyQuaternion(quaternion3D_XOY)
  114 + const endSphereXOY = endSphere.clone().applyQuaternion(quaternion3D_XOY)
  115 +
  116 + /*计算第二次旋转的四元数*/
  117 + // middleV3:startSphereXOY和endSphereXOY的中点
  118 + const middleV3 = startSphereXOY.clone().add(endSphereXOY).multiplyScalar(0.5)
  119 + const midDir = middleV3.clone().sub(origin).normalize() // 旋转前向量midDir,中点middleV3和球心构成的方向向量
  120 + const yDir = new Vector3(0, 1, 0) // 旋转后向量yDir,即y轴
  121 + // .setFromUnitVectors()计算从midDir向量旋转达到yDir向量所需要的四元数
  122 + // quaternion2表示让第一次旋转到XOY平面的起点和结束点关于y轴对称需要的四元数
  123 + const quaternionXOY_Y = new Quaternion().setFromUnitVectors(midDir, yDir)
  124 +
  125 + /*第二次旋转:使旋转到XOY平面的点再次旋转,实现关于Y轴对称*/
  126 + const startSpherXOY_Y = startSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
  127 + const endSphereXOY_Y = endSphereXOY.clone().applyQuaternion(quaternionXOY_Y)
  128 +
  129 + /**一个四元数表示一个旋转过程
  130 + *.invert()方法表示四元数的逆,简单说就是把旋转过程倒过来
  131 + * 两次旋转的四元数执行.invert()求逆,然后执行.multiply()相乘
  132 + *新版本.invert()对应旧版本.invert()
  133 + */
  134 + const quaternionInverse = quaternion3D_XOY.clone().invert().multiply(quaternionXOY_Y.clone().invert())
  135 + return {
  136 + // 返回两次旋转四元数的逆四元数
  137 + quaternion: quaternionInverse,
  138 + // 范围两次旋转后在XOY平面上关于y轴对称的圆弧起点和结束点坐标
  139 + startPoint: startSpherXOY_Y,
  140 + endPoint: endSphereXOY_Y
  141 + }
  142 +}
  143 +/**通过函数arcXOY()可以在XOY平面上绘制一个关于y轴对称的圆弧曲线
  144 + * startPoint, endPoint:表示圆弧曲线的起点和结束点坐标值,起点和结束点关于y轴对称
  145 + * 同时在圆弧轨迹的基础上绘制一段飞线*/
  146 +function arcXOY(radius, startPoint, endPoint, options) {
  147 + // 计算两点的中点
  148 + const middleV3 = new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5)
  149 + // 弦垂线的方向dir(弦的中点和圆心构成的向量)
  150 + const dir = middleV3.clone().normalize()
  151 + // 计算球面飞线的起点、结束点和球心构成夹角的弧度值
  152 + const earthRadianAngle = radianAOB(startPoint, endPoint, new Vector3(0, 0, 0))
  153 + /*设置飞线轨迹圆弧的中间点坐标
  154 + 弧度值 * radius * 0.2:表示飞线轨迹圆弧顶部距离地球球面的距离
  155 + 起点、结束点相聚越远,构成的弧线顶部距离球面越高*/
  156 + const arcTopCoord = dir.multiplyScalar(radius + earthRadianAngle * radius * 0.2) // 黄色飞行线的高度
  157 + //求三个点的外接圆圆心(飞线圆弧轨迹的圆心坐标)
  158 + const flyArcCenter = threePointCenter(startPoint, endPoint, arcTopCoord)
  159 + // 飞线圆弧轨迹半径flyArcR
  160 + const flyArcR = Math.abs(flyArcCenter.y - arcTopCoord.y)
  161 + /*坐标原点和飞线起点构成直线和y轴负半轴夹角弧度值
  162 + 参数分别是:飞线圆弧起点、y轴负半轴上一点、飞线圆弧圆心*/
  163 + const flyRadianAngle = radianAOB(startPoint, new Vector3(0, -1, 0), flyArcCenter)
  164 + const startAngle = -Math.PI / 2 + flyRadianAngle //飞线圆弧开始角度
  165 + const endAngle = Math.PI - startAngle //飞线圆弧结束角度
  166 + // 调用圆弧线模型的绘制函数
  167 + const arcline = circleLine(flyArcCenter.x, flyArcCenter.y, flyArcR, startAngle, endAngle, options.color)
  168 + // const arcline = new Group();// 不绘制轨迹线,使用 Group替换circleLine()即可
  169 + arcline.center = flyArcCenter //飞线圆弧自定一个属性表示飞线圆弧的圆心
  170 + arcline.topCoord = arcTopCoord //飞线圆弧自定一个属性表示飞线圆弧中间也就是顶部坐标
  171 +
  172 + // const flyAngle = Math.PI/ 10; //飞线圆弧固定弧度
  173 + const flyAngle = (endAngle - startAngle) / 7 //飞线圆弧的弧度和轨迹线弧度相关
  174 + // 绘制一段飞线,圆心做坐标原点
  175 + const flyLine = createFlyLine(flyArcR, startAngle, startAngle + flyAngle, options.flyLineColor)
  176 + flyLine.position.y = flyArcCenter.y //平移飞线圆弧和飞线轨迹圆弧重合
  177 + //飞线段flyLine作为飞线轨迹arcLine子对象,继承飞线轨迹平移旋转等变换
  178 + arcline.add(flyLine)
  179 + //飞线段运动范围startAngle~flyEndAngle
  180 + flyLine.flyEndAngle = endAngle - startAngle - flyAngle
  181 + flyLine.startAngle = startAngle
  182 + // arcline.flyEndAngle:飞线段当前角度位置,这里设置了一个随机值用于演示
  183 + flyLine.AngleZ = arcline.flyEndAngle * Math.random()
  184 + // flyLine.rotation.z = arcline.AngleZ;
  185 + // arcline.flyLine指向飞线段,便于设置动画是访问飞线段
  186 + arcline.userData['flyLine'] = flyLine
  187 +
  188 + return arcline
  189 +}
  190 +/*计算球面上两点和球心构成夹角的弧度值
  191 +参数point1, point2:表示地球球面上两点坐标Vector3
  192 +计算A、B两点和顶点O构成的AOB夹角弧度值*/
  193 +function radianAOB(A, B, O) {
  194 + // dir1、dir2:球面上两个点和球心构成的方向向量
  195 + const dir1 = A.clone().sub(O).normalize()
  196 + const dir2 = B.clone().sub(O).normalize()
  197 + //点乘.dot()计算夹角余弦值
  198 + const cosAngle = dir1.clone().dot(dir2)
  199 + const radianAngle = Math.acos(cosAngle) //余弦值转夹角弧度值,通过余弦值可以计算夹角范围是0~180度
  200 + return radianAngle
  201 +}
  202 +/*绘制一条圆弧曲线模型Line
  203 +5个参数含义:(圆心横坐标, 圆心纵坐标, 飞线圆弧轨迹半径, 开始角度, 结束角度)*/
  204 +function circleLine(x, y, r, startAngle, endAngle, color) {
  205 + const geometry = new BufferGeometry() //声明一个几何体对象Geometry
  206 + // ArcCurve创建圆弧曲线
  207 + const arc = new ArcCurve(x, y, r, startAngle, endAngle, false)
  208 + //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
  209 + const points = arc.getSpacedPoints(80) //分段数50,返回51个顶点
  210 + geometry.setFromPoints(points) // setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
  211 + const material = new LineBasicMaterial({
  212 + color: color || 0xd18547
  213 + }) //线条材质
  214 + const line = new Line(geometry, material) //线条模型对象
  215 + return line
  216 +}
  217 +//求三个点的外接圆圆心,p1, p2, p3表示三个点的坐标Vector3。
  218 +function threePointCenter(p1, p2, p3) {
  219 + const L1 = p1.lengthSq() //p1到坐标原点距离的平方
  220 + const L2 = p2.lengthSq()
  221 + const L3 = p3.lengthSq()
  222 + const x1 = p1.x,
  223 + y1 = p1.y,
  224 + x2 = p2.x,
  225 + y2 = p2.y,
  226 + x3 = p3.x,
  227 + y3 = p3.y
  228 + const S = x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2
  229 + const x = (L2 * y3 + L1 * y2 + L3 * y1 - L2 * y1 - L3 * y2 - L1 * y3) / S / 2
  230 + const y = (L3 * x2 + L2 * x1 + L1 * x3 - L1 * x2 - L2 * x3 - L3 * x1) / S / 2
  231 + // 三点外接圆圆心坐标
  232 + const center = new Vector3(x, y, 0)
  233 + return center
  234 +}
  235 +
  236 +export { arcXOY, flyArc }
... ...
  1 +import {
  2 + CatmullRomCurve3,
  3 + DoubleSide,
  4 + Group,
  5 + Mesh,
  6 + MeshBasicMaterial,
  7 + PlaneGeometry,
  8 + Texture,
  9 + TubeGeometry,
  10 + Vector3
  11 +} from 'three'
  12 +import { punctuation } from '../world/Earth'
  13 +
  14 +/**
  15 + * 经纬度坐标转球面坐标
  16 + * @param {地球半径} R
  17 + * @param {经度(角度值)} longitude
  18 + * @param {维度(角度值)} latitude
  19 + */
  20 +export const lon2xyz = (R: number, longitude: number, latitude: number): Vector3 => {
  21 + let lon = (longitude * Math.PI) / 180 // 转弧度值
  22 + const lat = (latitude * Math.PI) / 180 // 转弧度值
  23 + lon = -lon // js坐标系z坐标轴对应经度-90度,而不是90度
  24 +
  25 + // 经纬度坐标转球面坐标计算公式
  26 + const x = R * Math.cos(lat) * Math.cos(lon)
  27 + const y = R * Math.sin(lat)
  28 + const z = R * Math.cos(lat) * Math.sin(lon)
  29 + // 返回球面坐标
  30 + return new Vector3(x, y, z)
  31 +}
  32 +
  33 +// 创建波动光圈
  34 +export const createWaveMesh = (options: { radius: number; lon: number; lat: number; textures: any }) => {
  35 + const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
  36 + const texture = options.textures.aperture
  37 +
  38 + const material = new MeshBasicMaterial({
  39 + color: 0xe99f68,
  40 + map: texture,
  41 + transparent: true, //使用背景透明的png贴图,注意开启透明计算
  42 + opacity: 1.0,
  43 + depthWrite: false //禁止写入深度缓冲区数据
  44 + })
  45 + const mesh = new Mesh(geometry, material)
  46 + // 经纬度转球面坐标
  47 + const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
  48 + const size = options.radius * 0.12 //矩形平面Mesh的尺寸
  49 + mesh.scale.set(size, size, size) //设置mesh大小
  50 + mesh.userData['size'] = size //自顶一个属性,表示mesh静态大小
  51 + mesh.userData['scale'] = Math.random() * 1.0 //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~2倍之间变化
  52 + mesh.position.set(coord.x, coord.y, coord.z)
  53 + const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
  54 + const meshNormal = new Vector3(0, 0, 1)
  55 + mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
  56 + return mesh
  57 +}
  58 +
  59 +// 创建柱状
  60 +export const createLightPillar = (options: {
  61 + radius: number
  62 + lon: number
  63 + lat: number
  64 + index: number
  65 + textures: Record<string, Texture>
  66 + punctuation: punctuation
  67 +}) => {
  68 + const height = options.radius * 0.3
  69 + const geometry = new PlaneGeometry(options.radius * 0.05, height)
  70 + geometry.rotateX(Math.PI / 2)
  71 + geometry.translate(0, 0, height / 2)
  72 + const material = new MeshBasicMaterial({
  73 + map: options.textures.light_column,
  74 + color: options.index == 0 ? options.punctuation.lightColumn.startColor : options.punctuation.lightColumn.endColor,
  75 + transparent: true,
  76 + side: DoubleSide,
  77 + depthWrite: false //是否对深度缓冲区有任何的影响
  78 + })
  79 + const mesh = new Mesh(geometry, material)
  80 + const group = new Group()
  81 + // 两个光柱交叉叠加
  82 + group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)) //几何体绕x轴旋转了,所以mesh旋转轴变为z
  83 + // 经纬度转球面坐标
  84 + const SphereCoord = lon2xyz(options.radius, options.lon, options.lat) //SphereCoord球面坐标
  85 + group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z) //设置mesh位置
  86 + const coordVec3 = new Vector3(SphereCoord.x, SphereCoord.y, SphereCoord.z).normalize()
  87 + const meshNormal = new Vector3(0, 0, 1)
  88 + group.quaternion.setFromUnitVectors(meshNormal, coordVec3)
  89 + return group
  90 +}
  91 +
  92 +// 光柱底座矩形平面
  93 +export const createPointMesh = (options: { radius: number; lon: number; lat: number; material: MeshBasicMaterial }) => {
  94 + const geometry = new PlaneGeometry(1, 1) //默认在XOY平面上
  95 + const mesh = new Mesh(geometry, options.material)
  96 + // 经纬度转球面坐标
  97 + const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat)
  98 + const size = options.radius * 0.05 // 矩形平面Mesh的尺寸
  99 + mesh.scale.set(size, size, size) // 设置mesh大小
  100 +
  101 + // 设置mesh位置
  102 + mesh.position.set(coord.x, coord.y, coord.z)
  103 + const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize()
  104 + const meshNormal = new Vector3(0, 0, 1)
  105 + mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
  106 + return mesh
  107 +}
  108 +
  109 +// 获取点
  110 +export const getCirclePoints = (option: any) => {
  111 + const list = []
  112 + for (let j = 0; j < 2 * Math.PI - 0.1; j += (2 * Math.PI) / (option.number || 100)) {
  113 + list.push([
  114 + parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)),
  115 + 0,
  116 + parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2))
  117 + ])
  118 + }
  119 + if (option.closed) list.push(list[0])
  120 + return list
  121 +}
  122 +
  123 +// 创建线
  124 +
  125 +/**
  126 + * 创建动态的线
  127 + */
  128 +export const createAnimateLine = (option: any) => {
  129 + // 由多个点数组构成的曲线 通常用于道路
  130 + const l: Array<any> = []
  131 + option.pointList.forEach((e: Array<any>) => l.push(new Vector3(e[0], e[1], e[2])))
  132 + const curve = new CatmullRomCurve3(l) // 曲线路径
  133 +
  134 + // 管道体
  135 + const tubeGeometry = new TubeGeometry(curve, option.number || 50, option.radius || 1, option.radialSegments)
  136 + return new Mesh(tubeGeometry, option.material)
  137 +}
... ...
  1 +
  2 +export interface IEvents {
  3 + resize: () => void
  4 +}
... ...
  1 +export interface IWord {
  2 + dom: HTMLElement
  3 + data: any
  4 + width: number
  5 + height: number
  6 +}
\ No newline at end of file
... ...
  1 +uniform vec3 glowColor;
  2 +uniform float bias;
  3 +uniform float power;
  4 +uniform float time;
  5 +varying vec3 vp;
  6 +varying vec3 vNormal;
  7 +varying vec3 vPositionNormal;
  8 +uniform float scale;
  9 +// 获取纹理
  10 +uniform sampler2D map;
  11 +// 纹理坐标
  12 +varying vec2 vUv;
  13 +
  14 +void main(void){
  15 + float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power );
  16 + if(vp.y > time && vp.y < time + 20.0) {
  17 + float t = smoothstep(0.0, 0.8, (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0 );
  18 + gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t );
  19 + }
  20 + gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a);
  21 + float b = 0.8;
  22 + gl_FragColor = gl_FragColor + texture2D( map, vUv );
  23 +}
\ No newline at end of file
... ...
  1 +
  2 +varying vec2 vUv;
  3 +varying vec3 vNormal;
  4 +varying vec3 vp;
  5 +varying vec3 vPositionNormal;
  6 +void main(void){
  7 + vUv = uv;
  8 + vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
  9 + vp = position;
  10 + vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
  11 + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  12 +}
\ No newline at end of file
... ...
  1 +/**
  2 + * 资源文件
  3 + * 把模型和图片分开进行加载
  4 + */
  5 +
  6 +interface ITextures {
  7 + name: string
  8 + url: string
  9 +}
  10 +
  11 +export interface IResources {
  12 + textures?: ITextures[]
  13 +}
  14 +
  15 +const fileSuffix = ['earth', 'gradient', 'redCircle', 'label', 'aperture', 'glow', 'light_column', 'aircraft']
  16 +const textures: ITextures[] = []
  17 +
  18 +const modules = import.meta.globEager("../../images/earth/*");
  19 +
  20 +for(let item in modules) {
  21 + const n = item.split('/').pop()
  22 + if(n) {
  23 + textures.push({
  24 + name: n.split('.')[0],
  25 + url: modules[item].default
  26 + })
  27 + }
  28 +}
  29 +
  30 +const resources: IResources = {
  31 + textures
  32 +}
  33 +
  34 +export { resources }
... ...
  1 +/**
  2 + * 创建 threejs 四大天王
  3 + * 场景、相机、渲染器、控制器
  4 + */
  5 +
  6 +import * as THREE from 'three'
  7 +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  8 +
  9 +export class Basic {
  10 + public scene!: THREE.Scene
  11 + public camera!: THREE.PerspectiveCamera
  12 + public renderer!: THREE.WebGLRenderer
  13 + public controls!: OrbitControls
  14 + public dom: HTMLElement
  15 +
  16 + constructor(dom: HTMLElement) {
  17 + this.dom = dom
  18 + this.initScenes()
  19 + this.setControls()
  20 + }
  21 +
  22 + /**
  23 + * 初始化场景
  24 + */
  25 + initScenes() {
  26 + this.scene = new THREE.Scene()
  27 +
  28 + this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000)
  29 + this.camera.position.set(0, 30, -250)
  30 +
  31 + this.renderer = new THREE.WebGLRenderer({
  32 + // canvas: this.dom,
  33 + alpha: true, // 透明
  34 + antialias: true // 抗锯齿
  35 + })
  36 + this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比
  37 + this.renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器宽高
  38 + this.dom.appendChild(this.renderer.domElement) // 添加到dom中
  39 + }
  40 +
  41 + /**
  42 + * 设置控制器
  43 + */
  44 + setControls() {
  45 + // 鼠标控制 相机,渲染dom
  46 + this.controls = new OrbitControls(this.camera, this.renderer.domElement)
  47 +
  48 + this.controls.autoRotateSpeed = 3
  49 + // 使动画循环使用时阻尼或自转 意思是否有惯性
  50 + this.controls.enableDamping = true
  51 + // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
  52 + this.controls.dampingFactor = 0.05
  53 + // 是否可以缩放
  54 + this.controls.enableZoom = true
  55 + // 设置相机距离原点的最远距离
  56 + this.controls.minDistance = 100
  57 + // 设置相机距离原点的最远距离
  58 + this.controls.maxDistance = 300
  59 + // 是否开启右键拖拽
  60 + this.controls.enablePan = false
  61 + }
  62 +}
... ...
  1 +import {
  2 + BufferAttribute,
  3 + BufferGeometry,
  4 + Color,
  5 + DoubleSide,
  6 + Group,
  7 + Material,
  8 + Mesh,
  9 + MeshBasicMaterial,
  10 + NormalBlending,
  11 + Object3D,
  12 + Points,
  13 + PointsMaterial,
  14 + ShaderMaterial,
  15 + SphereGeometry,
  16 + Sprite,
  17 + SpriteMaterial,
  18 + Texture,
  19 + TextureLoader,
  20 + Vector3
  21 +} from 'three'
  22 +
  23 +import {
  24 + createAnimateLine,
  25 + createLightPillar,
  26 + createPointMesh,
  27 + createWaveMesh,
  28 + getCirclePoints,
  29 + lon2xyz
  30 +} from '../Utils/common'
  31 +import gsap from 'gsap'
  32 +import { flyArc } from '../Utils/arc'
  33 +import earthVertex from '../shaders/earth/vertex.vs?raw'
  34 +import earthFragment from '../shaders/earth/fragment.fs?raw'
  35 +
  36 +export type punctuation = {
  37 + circleColor: number
  38 + lightColumn: {
  39 + startColor: number // 起点颜色
  40 + endColor: number // 终点颜色
  41 + }
  42 +}
  43 +
  44 +type options = {
  45 + data: {
  46 + startArray: {
  47 + name: string
  48 + E: number // 经度
  49 + N: number // 维度
  50 + }
  51 + endArray: {
  52 + name: string
  53 + E: number // 经度
  54 + N: number // 维度
  55 + }[]
  56 + }[]
  57 + dom: HTMLElement
  58 + textures: Record<string, Texture> // 贴图
  59 + earth: {
  60 + radius: number // 地球半径
  61 + rotateSpeed: number // 地球旋转速度
  62 + isRotation: boolean // 地球组是否自转
  63 + }
  64 + satellite: {
  65 + show: boolean // 是否显示卫星
  66 + rotateSpeed: number // 旋转速度
  67 + size: number // 卫星大小
  68 + number: number // 一个圆环几个球
  69 + }
  70 + punctuation: punctuation
  71 + flyLine: {
  72 + color: number // 飞线的颜色
  73 + speed: number // 飞机拖尾线速度
  74 + flyLineColor: number // 飞行线的颜色
  75 + }
  76 +}
  77 +type uniforms = {
  78 + glowColor: { value: Color }
  79 + scale: { type: string; value: number }
  80 + bias: { type: string; value: number }
  81 + power: { type: string; value: number }
  82 + time: { type: string; value: any }
  83 + isHover: { value: boolean }
  84 + map: { value?: Texture }
  85 +}
  86 +
  87 +export default class earth {
  88 + public group: Group
  89 + public earthGroup: Group
  90 +
  91 + public around!: BufferGeometry
  92 + public aroundPoints!: Points<BufferGeometry, PointsMaterial>
  93 +
  94 + public options: options
  95 + public uniforms: uniforms
  96 + public timeValue: number
  97 +
  98 + public earth!: Mesh<SphereGeometry, ShaderMaterial>
  99 + public punctuationMaterial!: MeshBasicMaterial
  100 + public markupPoint: Group
  101 + public waveMeshArr: Object3D[]
  102 +
  103 + public circleLineList: any[]
  104 + public circleList: any[]
  105 + public x: number
  106 + public n: number
  107 + public isRotation: boolean
  108 + public flyLineArcGroup!: Group
  109 +
  110 + constructor(options: options) {
  111 + this.options = options
  112 +
  113 + this.group = new Group()
  114 + this.group.name = 'group'
  115 + this.group.scale.set(0, 0, 0)
  116 + this.earthGroup = new Group()
  117 + this.group.add(this.earthGroup)
  118 + this.earthGroup.name = 'EarthGroup'
  119 +
  120 + // 标注点效果
  121 + this.markupPoint = new Group()
  122 + this.markupPoint.name = 'markupPoint'
  123 + this.waveMeshArr = []
  124 +
  125 + // 卫星和标签
  126 + this.circleLineList = []
  127 + this.circleList = []
  128 + this.x = 0
  129 + this.n = 0
  130 +
  131 + // 地球自转
  132 + this.isRotation = this.options.earth.isRotation
  133 +
  134 + // 扫光动画 shader
  135 + this.timeValue = 200
  136 +
  137 + this.uniforms = {
  138 + glowColor: {
  139 + value: new Color(0x0cd1eb)
  140 + },
  141 + scale: {
  142 + type: 'f',
  143 + value: -1.0
  144 + },
  145 + bias: {
  146 + type: 'f',
  147 + value: 1.0
  148 + },
  149 + power: {
  150 + type: 'f',
  151 + value: 3.3
  152 + },
  153 + time: {
  154 + type: 'f',
  155 + value: this.timeValue
  156 + },
  157 + isHover: {
  158 + value: false
  159 + },
  160 + map: {
  161 + value: undefined
  162 + }
  163 + }
  164 + }
  165 +
  166 + async init(): Promise<void> {
  167 + return new Promise(resolve => {
  168 + const init = async () => {
  169 + this.createEarth() // 创建地球
  170 + this.createEarthGlow() // 创建地球辉光
  171 + this.createEarthAperture() // 创建地球的大气层
  172 + await this.createMarkupPoint() // 创建柱状点位
  173 + this.createAnimateCircle() // 创建环绕卫星
  174 + this.createFlyLine() // 创建飞线
  175 + this.show()
  176 + resolve()
  177 + }
  178 + init()
  179 + })
  180 + }
  181 +
  182 + createEarth() {
  183 + const earth_geometry = new SphereGeometry(this.options.earth.radius, 50, 50)
  184 + const earth_border = new SphereGeometry(this.options.earth.radius + 10, 60, 60)
  185 +
  186 + const pointMaterial = new PointsMaterial({
  187 + color: 0x81ffff, //设置颜色,默认 0xFFFFFF
  188 + transparent: true,
  189 + sizeAttenuation: true,
  190 + opacity: 0.1,
  191 + vertexColors: false, //定义材料是否使用顶点颜色,默认false ---如果该选项设置为true,则color属性失效
  192 + size: 0.2 //定义粒子的大小。默认为1.0
  193 + })
  194 + const points = new Points(earth_border, pointMaterial) //将模型添加到场景
  195 +
  196 + this.earthGroup.add(points)
  197 +
  198 + this.uniforms.map.value = this.options.textures.earth
  199 +
  200 + const earth_material = new ShaderMaterial({
  201 + // wireframe:true, // 显示模型线条
  202 + uniforms: this.uniforms as any,
  203 + vertexShader: earthVertex,
  204 + fragmentShader: earthFragment
  205 + })
  206 +
  207 + earth_material.needsUpdate = true
  208 + this.earth = new Mesh(earth_geometry, earth_material)
  209 + this.earth.name = 'earth'
  210 + this.earthGroup.add(this.earth)
  211 + }
  212 +
  213 + createEarthGlow() {
  214 + const R = this.options.earth.radius //地球半径
  215 +
  216 + // TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图
  217 + const texture = this.options.textures.glow // 加载纹理贴图
  218 +
  219 + // 创建精灵材质对象SpriteMaterial
  220 + const spriteMaterial = new SpriteMaterial({
  221 + map: texture, // 设置精灵纹理贴图
  222 + color: 0x4390d1,
  223 + transparent: true, //开启透明
  224 + opacity: 0.7, // 可以通过透明度整体调节光圈
  225 + depthWrite: false //禁止写入深度缓冲区数据
  226 + })
  227 +
  228 + // 创建表示地球光圈的精灵模型
  229 + const sprite = new Sprite(spriteMaterial)
  230 + sprite.scale.set(R * 3.0, R * 3.0, 1) //适当缩放精灵
  231 + this.earthGroup.add(sprite)
  232 + }
  233 +
  234 + createEarthAperture() {
  235 + const vertexShader = [
  236 + 'varying vec3 vVertexWorldPosition;',
  237 + 'varying vec3 vVertexNormal;',
  238 + 'varying vec4 vFragColor;',
  239 + 'void main(){',
  240 + ' vVertexNormal = normalize(normalMatrix * normal);', //将法线转换到视图坐标系中
  241 + ' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;', //将顶点转换到世界坐标系中
  242 + ' // set gl_Position',
  243 + ' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
  244 + '}'
  245 + ].join('\n')
  246 +
  247 + //大气层效果
  248 + const AeroSphere = {
  249 + uniforms: {
  250 + coeficient: {
  251 + type: 'f',
  252 + value: 1.0
  253 + },
  254 + power: {
  255 + type: 'f',
  256 + value: 3
  257 + },
  258 + glowColor: {
  259 + type: 'c',
  260 + value: new Color(0x4390d1)
  261 + }
  262 + },
  263 + vertexShader: vertexShader,
  264 + fragmentShader: [
  265 + 'uniform vec3 glowColor;',
  266 + 'uniform float coeficient;',
  267 + 'uniform float power;',
  268 +
  269 + 'varying vec3 vVertexNormal;',
  270 + 'varying vec3 vVertexWorldPosition;',
  271 +
  272 + 'varying vec4 vFragColor;',
  273 +
  274 + 'void main(){',
  275 + ' vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机位置到顶点位置的距离
  276 + ' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;', //视图坐标系中从相机位置到顶点位置的距离
  277 + ' viewCameraToVertex= normalize(viewCameraToVertex);', //规一化
  278 + ' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);',
  279 + ' gl_FragColor = vec4(glowColor, intensity);',
  280 + '}'
  281 + ].join('\n')
  282 + }
  283 + //球体 辉光 大气层
  284 + const material1 = new ShaderMaterial({
  285 + uniforms: AeroSphere.uniforms,
  286 + vertexShader: AeroSphere.vertexShader,
  287 + fragmentShader: AeroSphere.fragmentShader,
  288 + blending: NormalBlending,
  289 + transparent: true,
  290 + depthWrite: false
  291 + })
  292 + const sphere = new SphereGeometry(this.options.earth.radius + 0, 50, 50)
  293 + const mesh = new Mesh(sphere, material1)
  294 + this.earthGroup.add(mesh)
  295 + }
  296 +
  297 + async createMarkupPoint() {
  298 + await Promise.all(
  299 + this.options.data.map(async item => {
  300 + const radius = this.options.earth.radius
  301 + const lon = item.startArray.E //经度
  302 + const lat = item.startArray.N //纬度
  303 +
  304 + this.punctuationMaterial = new MeshBasicMaterial({
  305 + color: this.options.punctuation.circleColor,
  306 + map: this.options.textures.label,
  307 + transparent: true, //使用背景透明的png贴图,注意开启透明计算
  308 + depthWrite: false //禁止写入深度缓冲区数据
  309 + })
  310 +
  311 + const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
  312 + this.markupPoint.add(mesh)
  313 + const LightPillar = createLightPillar({
  314 + radius: this.options.earth.radius,
  315 + lon,
  316 + lat,
  317 + index: 0,
  318 + textures: this.options.textures,
  319 + punctuation: this.options.punctuation
  320 + }) //光柱
  321 + this.markupPoint.add(LightPillar)
  322 + const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
  323 + this.markupPoint.add(WaveMesh)
  324 + this.waveMeshArr.push(WaveMesh)
  325 +
  326 + await Promise.all(
  327 + item.endArray.map(obj => {
  328 + const lon = obj.E //经度
  329 + const lat = obj.N //纬度
  330 + const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }) //光柱底座矩形平面
  331 + this.markupPoint.add(mesh)
  332 + const LightPillar = createLightPillar({
  333 + radius: this.options.earth.radius,
  334 + lon,
  335 + lat,
  336 + index: 1,
  337 + textures: this.options.textures,
  338 + punctuation: this.options.punctuation
  339 + }) //光柱
  340 + this.markupPoint.add(LightPillar)
  341 + const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }) //波动光圈
  342 + this.markupPoint.add(WaveMesh)
  343 + this.waveMeshArr.push(WaveMesh)
  344 + })
  345 + )
  346 + this.earthGroup.add(this.markupPoint)
  347 + })
  348 + )
  349 + }
  350 +
  351 + createAnimateCircle() {
  352 + // 创建 圆环 点
  353 + const list = getCirclePoints({
  354 + radius: this.options.earth.radius + 15,
  355 + number: 150, //切割数
  356 + closed: true // 闭合
  357 + })
  358 + const mat = new MeshBasicMaterial({
  359 + color: '#0c3172',
  360 + transparent: true,
  361 + opacity: 0.4,
  362 + side: DoubleSide
  363 + })
  364 + const line = createAnimateLine({
  365 + pointList: list,
  366 + material: mat,
  367 + number: 100,
  368 + radius: 0.1
  369 + })
  370 + this.earthGroup.add(line)
  371 +
  372 + // 在clone两条线出来
  373 + const l2 = line.clone()
  374 + l2.scale.set(1.2, 1.2, 1.2)
  375 + l2.rotateZ(Math.PI / 6)
  376 + this.earthGroup.add(l2)
  377 +
  378 + const l3 = line.clone()
  379 + l3.scale.set(0.8, 0.8, 0.8)
  380 + l3.rotateZ(-Math.PI / 6)
  381 + this.earthGroup.add(l3)
  382 +
  383 + /**
  384 + * 旋转的球
  385 + */
  386 + const ball = new Mesh(
  387 + new SphereGeometry(this.options.satellite.size, 32, 32),
  388 + new MeshBasicMaterial({
  389 + color: '#e0b187' // 745F4D
  390 + })
  391 + )
  392 +
  393 + const ball2 = new Mesh(
  394 + new SphereGeometry(this.options.satellite.size, 32, 32),
  395 + new MeshBasicMaterial({
  396 + color: '#628fbb' // 324A62
  397 + })
  398 + )
  399 +
  400 + const ball3 = new Mesh(
  401 + new SphereGeometry(this.options.satellite.size, 32, 32),
  402 + new MeshBasicMaterial({
  403 + color: '#806bdf' //6D5AC4
  404 + })
  405 + )
  406 +
  407 + this.circleLineList.push(line, l2, l3)
  408 + ball.name = ball2.name = ball3.name = '卫星'
  409 +
  410 + for (let i = 0; i < this.options.satellite.number; i++) {
  411 + const ball01 = ball.clone()
  412 + // 一根线上总共有几个球,根据数量平均分布一下
  413 + const num = Math.floor(list.length / this.options.satellite.number)
  414 + ball01.position.set(list[num * (i + 1)][0] * 1, list[num * (i + 1)][1] * 1, list[num * (i + 1)][2] * 1)
  415 + line.add(ball01)
  416 +
  417 + const ball02 = ball2.clone()
  418 + const num02 = Math.floor(list.length / this.options.satellite.number)
  419 + ball02.position.set(list[num02 * (i + 1)][0] * 1, list[num02 * (i + 1)][1] * 1, list[num02 * (i + 1)][2] * 1)
  420 + l2.add(ball02)
  421 +
  422 + const ball03 = ball2.clone()
  423 + const num03 = Math.floor(list.length / this.options.satellite.number)
  424 + ball03.position.set(list[num03 * (i + 1)][0] * 1, list[num03 * (i + 1)][1] * 1, list[num03 * (i + 1)][2] * 1)
  425 + l3.add(ball03)
  426 + }
  427 + }
  428 +
  429 + createFlyLine() {
  430 + this.flyLineArcGroup = new Group()
  431 + this.flyLineArcGroup.userData['flyLineArray'] = []
  432 + this.earthGroup.add(this.flyLineArcGroup)
  433 + this.options.data.forEach(cities => {
  434 + cities.endArray.forEach(item => {
  435 + // 调用函数flyArc绘制球面上任意两点之间飞线圆弧轨迹
  436 + const arcline = flyArc(
  437 + this.options.earth.radius,
  438 + cities.startArray.E,
  439 + cities.startArray.N,
  440 + item.E,
  441 + item.N,
  442 + this.options.flyLine
  443 + )
  444 +
  445 + this.flyLineArcGroup.add(arcline) // 飞线插入flyArcGroup中
  446 + this.flyLineArcGroup.userData['flyLineArray'].push(arcline.userData['flyLine'])
  447 + })
  448 + })
  449 + }
  450 +
  451 + show() {
  452 + gsap.to(this.group.scale, {
  453 + x: 1,
  454 + y: 1,
  455 + z: 1,
  456 + duration: 2,
  457 + ease: 'Quadratic'
  458 + })
  459 + }
  460 +
  461 + render() {
  462 + this.flyLineArcGroup?.userData['flyLineArray']?.forEach((fly: any) => {
  463 + fly.rotation.z += this.options.flyLine.speed // 调节飞线速度
  464 + if (fly.rotation.z >= fly.flyEndAngle) fly.rotation.z = 0
  465 + })
  466 +
  467 + if (this.isRotation) {
  468 + this.earthGroup.rotation.y += this.options.earth.rotateSpeed
  469 + }
  470 +
  471 + this.circleLineList.forEach(e => {
  472 + e.rotateY(this.options.satellite.rotateSpeed)
  473 + })
  474 +
  475 + this.uniforms.time.value =
  476 + this.uniforms.time.value < -this.timeValue ? this.timeValue : this.uniforms.time.value - 1
  477 +
  478 + if (this.waveMeshArr.length) {
  479 + this.waveMeshArr.forEach((mesh: any) => {
  480 + mesh.userData['scale'] += 0.007
  481 + mesh.scale.set(
  482 + mesh.userData['size'] * mesh.userData['scale'],
  483 + mesh.userData['size'] * mesh.userData['scale'],
  484 + mesh.userData['size'] * mesh.userData['scale']
  485 + )
  486 + if (mesh.userData['scale'] <= 1.5) {
  487 + (mesh.material as Material).opacity = (mesh.userData['scale'] - 1) * 2 //2等于1/(1.5-1.0),保证透明度在0~1之间变化
  488 + } else if (mesh.userData['scale'] > 1.5 && mesh.userData['scale'] <= 2) {
  489 + (mesh.material as Material).opacity = 1 - (mesh.userData['scale'] - 1.5) * 2 //2等于1/(2.0-1.5) mesh缩放2倍对应0 缩放1.5被对应1
  490 + } else {
  491 + mesh.userData['scale'] = 1
  492 + }
  493 + })
  494 + }
  495 + }
  496 +}
... ...
  1 +/**
  2 + * 资源管理和加载
  3 + */
  4 +import { LoadingManager, Texture, TextureLoader } from 'three'
  5 +import { loadingStart, loadingFinish, loadingError } from '@/utils'
  6 +import { resources } from './Assets'
  7 +export class Resources {
  8 + private manager!: LoadingManager
  9 + private callback: () => void
  10 + private textureLoader!: InstanceType<typeof TextureLoader>
  11 + public textures: Record<string, Texture>
  12 + constructor(callback: () => void) {
  13 + this.callback = callback // 资源加载完成的回调
  14 + this.textures = {} // 贴图对象
  15 + this.setLoadingManager()
  16 + this.loadResources()
  17 + }
  18 +
  19 + /**
  20 + * 管理加载状态
  21 + */
  22 + private setLoadingManager() {
  23 + this.manager = new LoadingManager()
  24 + // 开始加载
  25 + this.manager.onStart = () => {
  26 + loadingStart()
  27 + }
  28 + // 加载完成
  29 + this.manager.onLoad = () => {
  30 + this.callback()
  31 + }
  32 + // 正在进行中
  33 + this.manager.onProgress = url => {
  34 + loadingFinish()
  35 + }
  36 +
  37 + this.manager.onError = url => {
  38 + loadingError()
  39 + window['$message'].error('数据加载失败,请刷新重试!')
  40 + }
  41 + }
  42 +
  43 + /**
  44 + * 加载资源
  45 + */
  46 + private loadResources(): void {
  47 + this.textureLoader = new TextureLoader(this.manager)
  48 + resources.textures?.forEach(item => {
  49 + this.textureLoader.load(item.url, t => {
  50 + this.textures[item.name] = t
  51 + })
  52 + })
  53 + }
  54 +}
... ...
  1 +import { MeshBasicMaterial, PerspectiveCamera, Scene, ShaderMaterial, WebGLRenderer } from 'three'
  2 +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  3 +// interfaces
  4 +import { IWord } from '../interfaces/IWord'
  5 +import { Basic } from './Basic'
  6 +import { Resources } from './Resources'
  7 +// earth
  8 +import Earth from './Earth'
  9 +
  10 +export default class World {
  11 + public basic: Basic
  12 + public scene: Scene
  13 + public camera: PerspectiveCamera
  14 + public renderer: WebGLRenderer
  15 + public controls: OrbitControls
  16 + public material!: ShaderMaterial | MeshBasicMaterial
  17 + public resources: Resources
  18 + public option: IWord
  19 + public earth!: Earth
  20 +
  21 + constructor(option: IWord) {
  22 + /**
  23 + * 加载资源
  24 + */
  25 + this.option = option
  26 + this.basic = new Basic(option.dom)
  27 + this.scene = this.basic.scene
  28 + this.renderer = this.basic.renderer
  29 + this.controls = this.basic.controls
  30 + this.camera = this.basic.camera
  31 + this.updateSize()
  32 + this.resources = new Resources(async () => {
  33 + await this.createEarth()
  34 + // 开始渲染
  35 + this.render()
  36 + })
  37 + }
  38 +
  39 + async createEarth(data?: any) {
  40 + // 资源加载完成,开始制作地球,注释在new Earth()类型里面
  41 + this.earth = new Earth({
  42 + data: data || this.option.data,
  43 + dom: this.option.dom,
  44 + textures: this.resources.textures,
  45 + earth: {
  46 + radius: 50,
  47 + rotateSpeed: 0.002,
  48 + isRotation: true
  49 + },
  50 + satellite: {
  51 + show: true,
  52 + rotateSpeed: -0.01,
  53 + size: 1,
  54 + number: 2
  55 + },
  56 + punctuation: {
  57 + circleColor: 0x3892ff,
  58 + lightColumn: {
  59 + startColor: 0xe4007f, // 起点颜色
  60 + endColor: 0xffffff // 终点颜色
  61 + }
  62 + },
  63 + flyLine: {
  64 + color: 0xf3ae76, // 飞线的颜色
  65 + flyLineColor: 0xff7714, // 飞行线的颜色
  66 + speed: 0.004 // 拖尾飞线的速度
  67 + }
  68 + })
  69 +
  70 + this.scene.add(this.earth.group)
  71 + await this.earth.init()
  72 + }
  73 +
  74 + /**
  75 + * 渲染函数
  76 + */
  77 + public render() {
  78 + requestAnimationFrame(this.render.bind(this))
  79 + this.renderer.render(this.scene, this.camera)
  80 + this.controls && this.controls.update()
  81 + this.earth && this.earth.render()
  82 + }
  83 +
  84 + // 更新
  85 + public updateSize(width?: number, height?: number) {
  86 + let w = width || this.option.width
  87 + let h = height || this.option.height
  88 + // 取小值
  89 + if (w < h) h = w
  90 + else w = h
  91 +
  92 + this.renderer.setSize(w, h)
  93 + this.camera.aspect = w / h
  94 + this.camera.updateProjectionMatrix()
  95 + }
  96 +
  97 + // 数据更新重新渲染
  98 + public updateData(data?: any) {
  99 + if (!this.earth.group) return
  100 + // 先删除旧的
  101 + this.scene.remove(this.earth.group)
  102 + // 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存
  103 + this.earth.group.traverse((obj: any) => {
  104 + if (obj.type === 'Mesh') {
  105 + obj.geometry.dispose()
  106 + obj.material.dispose()
  107 + }
  108 + })
  109 + // 重新创建
  110 + this.createEarth(data)
  111 + }
  112 +}
... ...
  1 +import { PublicConfigClass } from '@/packages/public'
  2 +import { CreateComponentType } from '@/packages/index.d'
  3 +import { chartInitConfig } from '@/settings/designSetting'
  4 +import { ThreeEarth01Config } from './index'
  5 +import dataJson from './data.json'
  6 +import cloneDeep from 'lodash/cloneDeep'
  7 +
  8 +export const option = {
  9 + dataset: dataJson
  10 +}
  11 +
  12 +export default class Config extends PublicConfigClass implements CreateComponentType {
  13 + public key = ThreeEarth01Config.key
  14 + public attr = { ...chartInitConfig, w: 800, h: 800, zIndex: -1 }
  15 + public chartConfig = cloneDeep(ThreeEarth01Config)
  16 + public option = cloneDeep(option)
  17 +}
... ...
  1 +<template></template>
  2 +
  3 +<script setup lang="ts">
  4 +import { PropType } from 'vue'
  5 +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
  6 +import { option } from './config.vue'
  7 +
  8 +const props = defineProps({
  9 + optionData: {
  10 + type: Object as PropType<typeof option>,
  11 + required: true
  12 + }
  13 +})
  14 +</script>
... ...
  1 +[
  2 + {
  3 + "startArray": {
  4 + "name": "杭州",
  5 + "N": 30.246026,
  6 + "E": 120.210792
  7 + },
  8 + "endArray": [
  9 + {
  10 + "name": "曼谷",
  11 + "N": 22,
  12 + "E": 100.49074172973633
  13 + },
  14 + {
  15 + "name": "澳大利亚",
  16 + "N": -23.68477416688374,
  17 + "E": 133.857421875
  18 + },
  19 +
  20 + {
  21 + "name": "新疆维吾尔自治区",
  22 + "N": 41.748,
  23 + "E": 84.9023
  24 + },
  25 +
  26 + {
  27 + "name": "德黑兰",
  28 + "N": 35,
  29 + "E": 51
  30 + },
  31 + {
  32 + "name": "德黑兰",
  33 + "N": 35,
  34 + "E": 51
  35 + },
  36 + {
  37 + "name": "美国",
  38 + "N": 34.125447565116126,
  39 + "E": 241.7431640625
  40 + },
  41 + {
  42 + "name": "英国",
  43 + "N": 51.508742458803326,
  44 + "E": 359.82421875
  45 + },
  46 + {
  47 + "name": "巴西",
  48 + "N": -9.96885060854611,
  49 + "E": 668.1445312499999
  50 + }
  51 + ]
  52 + },
  53 + {
  54 + "startArray": {
  55 + "name": "北京",
  56 + "N": 39.89491,
  57 + "E": 116.322056
  58 + },
  59 + "endArray": [
  60 + {
  61 + "name": "西藏",
  62 + "N": 29.660361,
  63 + "E": 91.132212
  64 + },
  65 + {
  66 + "name": "广西",
  67 + "N": 22.830824,
  68 + "E": 108.30616
  69 + },
  70 +
  71 + {
  72 + "name": "江西",
  73 + "N": 28.676493,
  74 + "E": 115.892151
  75 + },
  76 +
  77 + {
  78 + "name": "贵阳",
  79 + "N": 26.647661,
  80 + "E": 106.630153
  81 + }
  82 + ]
  83 + }
  84 +]
... ...
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
  3 +
  4 +export const ThreeEarth01Config: ConfigType = {
  5 + key: 'ThreeEarth01',
  6 + chartKey: 'VThreeEarth01',
  7 + conKey: 'VCThreeEarth01',
  8 + title: '三维地球',
  9 + category: ChatCategoryEnum.THREEDANIMATION,
  10 + categoryName: ChatCategoryEnumName.THREEDANIMATION,
  11 + package: PackagesCategoryEnum.ARTOONS,
  12 + chartFrame: ChartFrameEnum.STATIC,
  13 + image: 'threeEarth01.png'
  14 +}
... ...
  1 +<template>
  2 + <div ref="chartRef"></div>
  3 +</template>
  4 +
  5 +<script setup lang="ts">
  6 +import { onMounted, PropType, ref, toRefs, watch } from 'vue'
  7 +import { CreateComponentType } from '@/packages'
  8 +import { useChartDataFetch } from '@/hooks'
  9 +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  10 +import { option } from './config.vue'
  11 +import World from './code/world/Word'
  12 +import throttle from 'lodash/throttle'
  13 +
  14 +const props = defineProps({
  15 + chartConfig: {
  16 + type: Object as PropType<CreateComponentType & typeof option>,
  17 + required: true
  18 + }
  19 +})
  20 +
  21 +const chartEditStore = useChartEditStore()
  22 +
  23 +const chartRef = ref<HTMLElement>()
  24 +const { w, h } = toRefs(props.chartConfig.attr)
  25 +let threeClassInstance: World
  26 +
  27 +// 初始化
  28 +const init = () => {
  29 + const dom: HTMLElement | undefined = chartRef.value
  30 + if (dom) {
  31 + threeClassInstance = new World({
  32 + dom: dom,
  33 + data: props.chartConfig.option.dataset,
  34 + width: w.value,
  35 + height: h.value
  36 + })
  37 + }
  38 +}
  39 +
  40 +const updateData = (data: any) => {
  41 + try {
  42 + threeClassInstance.updateData(data)
  43 + } catch (error) {
  44 + console.log(error)
  45 + }
  46 +}
  47 +
  48 +// 改变大小
  49 +watch(
  50 + () => [w.value, h.value],
  51 + throttle(([newWidth], [newHeight]) => {
  52 + threeClassInstance.updateSize(newWidth, newHeight)
  53 + }, 100)
  54 +)
  55 +
  56 +watch(
  57 + () => props.chartConfig.option.dataset,
  58 + (newData: any) => {
  59 + updateData(newData)
  60 + },
  61 + {
  62 + deep: false
  63 + }
  64 +)
  65 +
  66 +// DOM 渲染之后进行初始化
  67 +onMounted(() => {
  68 + try {
  69 + if (navigator.userAgent.indexOf('Chrome') < -1 || navigator.userAgent.indexOf('Edg') < -1) {
  70 + window['$message'].error('三维地图组件仅在【谷歌】浏览器上能正常展示!')
  71 + chartEditStore.removeComponentList(undefined, false)
  72 + return
  73 + }
  74 + init()
  75 + } catch (error) {
  76 + console.log(error)
  77 + }
  78 +})
  79 +
  80 +useChartDataFetch(props.chartConfig, useChartEditStore, updateData)
  81 +</script>
... ...
  1 +import {ThreeEarth01Config} from './ThreeEarth01/index'
  2 +
  3 +
  4 +export default [ThreeEarth01Config]
... ...
  1 +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
  2 +import { CreateComponentType } from '@/packages/index.d'
  3 +import { WaterPoloConfig } from './index'
  4 +import cloneDeep from 'lodash/cloneDeep'
  5 +
  6 +export const shapes = [
  7 + {
  8 + label: '圆形',
  9 + value: 'circle'
  10 + },
  11 + {
  12 + label: '正方形',
  13 + value: 'rect'
  14 + },
  15 + {
  16 + label: '带圆角的正方形',
  17 + value: 'roundRect'
  18 + },
  19 + {
  20 + label: '正三角形',
  21 + value: 'triangle'
  22 + },
  23 + {
  24 + label: '菱形',
  25 + value: 'diamond'
  26 + },
  27 + {
  28 + label: '水滴',
  29 + value: 'pin'
  30 + },
  31 + {
  32 + label: '箭头',
  33 + value: 'arrow'
  34 + },
  35 +]
  36 +
  37 +export const includes = []
  38 +
  39 +export const option = {
  40 + dataset: 0.5,
  41 + series: [
  42 + {
  43 + type: 'liquidFill',
  44 + shape: shapes[0].value,
  45 + radius: '90%',
  46 + data: [0],
  47 + center: ['50%', '50%'],
  48 + color: [
  49 + {
  50 + type: 'linear',
  51 + x: 0,
  52 + y: 0,
  53 + x2: 0,
  54 + y2: 1,
  55 + colorStops: [
  56 + {
  57 + offset: 0,
  58 + color: '#446bf5',
  59 + },
  60 + {
  61 + offset: 1,
  62 + color: '#2ca3e2',
  63 + },
  64 + ],
  65 + globalCoord: false,
  66 + },
  67 + ],
  68 + backgroundStyle: {
  69 + borderWidth: 1,
  70 + color: 'rgba(51, 66, 127, 0.7)',
  71 + },
  72 + label: {
  73 + normal: {
  74 + textStyle: {
  75 + fontSize: 50,
  76 + color: '#fff',
  77 + },
  78 + },
  79 + },
  80 + outline: {
  81 + show: false,
  82 + borderDistance: 10,
  83 + itemStyle: {
  84 + borderWidth: 2,
  85 + borderColor: '#112165'
  86 + }
  87 + }
  88 + }
  89 + ]
  90 +}
  91 +
  92 +export default class Config extends PublicConfigClass implements CreateComponentType
  93 +{
  94 + public key = WaterPoloConfig.key
  95 + public chartConfig = cloneDeep(WaterPoloConfig)
  96 + public option = echartOptionProfixHandle(option, includes)
  97 +}
... ...
  1 +<template>
  2 + <CollapseItem
  3 + v-for="(item, index) in seriesList"
  4 + :key="index"
  5 + name="水球"
  6 + :expanded="true"
  7 + >
  8 + <SettingItemBox name="内容">
  9 + <SettingItem name="数值">
  10 + <n-input-number
  11 + v-model:value="item.data[0]"
  12 + :min="0"
  13 + :step="0.01"
  14 + size="small"
  15 + placeholder="水球数值"
  16 + ></n-input-number>
  17 + </SettingItem>
  18 + <SettingItem name="形状">
  19 + <n-select v-model:value="item.shape" :options="shapes" placeholder="选择形状" />
  20 + </SettingItem>
  21 + <SettingItem name="文本">
  22 + <n-input-number v-model:value="item.label.normal.textStyle.fontSize" :min="0" :step="1" size="small" placeholder="文字大小">
  23 + </n-input-number>
  24 + </SettingItem>
  25 + <SettingItem name="颜色1">
  26 + <n-color-picker
  27 + size="small"
  28 + :modes="['hex']"
  29 + v-model:value="item.color[0].colorStops[0].color"
  30 + ></n-color-picker>
  31 + </SettingItem>
  32 + <SettingItem name="颜色2">
  33 + <n-color-picker
  34 + size="small"
  35 + :modes="['hex']"
  36 + v-model:value="item.color[0].colorStops[1].color"
  37 + ></n-color-picker>
  38 + </SettingItem>
  39 + </SettingItemBox>
  40 + <SettingItemBox name="背景" :alone="true">
  41 + <SettingItem>
  42 + <n-color-picker
  43 + size="small"
  44 + :modes="['hex']"
  45 + v-model:value="item.backgroundStyle.color"
  46 + ></n-color-picker>
  47 + </SettingItem>
  48 + </SettingItemBox>
  49 + </CollapseItem>
  50 +</template>
  51 +
  52 +<script setup lang="ts">
  53 +import { PropType, computed } from 'vue'
  54 +import { option, shapes } from './config'
  55 +import {
  56 + CollapseItem,
  57 + SettingItemBox,
  58 + SettingItem,
  59 +} from '@/components/Pages/ChartItemSetting'
  60 +
  61 +const props = defineProps({
  62 + optionData: {
  63 + type: Object as PropType<typeof option>,
  64 + required: true,
  65 + },
  66 +})
  67 +
  68 +const seriesList = computed(() => {
  69 + return props.optionData.series
  70 +})
  71 +</script>
... ...
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
  3 +
  4 +export const WaterPoloConfig: ConfigType = {
  5 + key: 'WaterPolo',
  6 + chartKey: 'VWaterPolo',
  7 + conKey: 'VCWaterPolo',
  8 + title: '水球图',
  9 + category: ChatCategoryEnum.TWODANIMATION,
  10 + categoryName: ChatCategoryEnumName.TWODANIMATION,
  11 + package: PackagesCategoryEnum.ARTOONS,
  12 + chartFrame: ChartFrameEnum.COMMON,
  13 + image: 'water_WaterPolo.png'
  14 +}
... ...
  1 +<template>
  2 + <v-chart :theme="themeColor" :option="option.value" autoresize></v-chart>
  3 +</template>
  4 +
  5 +<script setup lang="ts">
  6 +import { PropType, watch, reactive } from 'vue'
  7 +import VChart from 'vue-echarts'
  8 +import { use } from 'echarts/core'
  9 +import 'echarts-liquidfill/src/liquidFill.js'
  10 +import { CanvasRenderer } from 'echarts/renderers'
  11 +import { GridComponent } from 'echarts/components'
  12 +import config from './config'
  13 +import { isPreview, isString, isNumber } from '@/utils'
  14 +import { chartColorsSearch, defaultTheme } from '@/settings/chartThemes/index'
  15 +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  16 +import { useChartDataFetch } from '@/hooks'
  17 +
  18 +const props = defineProps({
  19 + themeSetting: {
  20 + type: Object,
  21 + required: true
  22 + },
  23 + themeColor: {
  24 + type: Object,
  25 + required: true
  26 + },
  27 + chartConfig: {
  28 + type: Object as PropType<config>,
  29 + required: true
  30 + }
  31 +})
  32 +
  33 +use([CanvasRenderer, GridComponent])
  34 +
  35 +const chartEditStore = useChartEditStore()
  36 +
  37 +const option = reactive({
  38 + value: {}
  39 +})
  40 +
  41 +// 渐变色处理
  42 +watch(
  43 + () => chartEditStore.getEditCanvasConfig.chartThemeColor,
  44 + (newColor: keyof typeof chartColorsSearch) => {
  45 + try {
  46 + if (!isPreview()) {
  47 + const themeColor = chartColorsSearch[newColor] || chartColorsSearch[defaultTheme]
  48 + // 背景颜色
  49 + props.chartConfig.option.series[0].backgroundStyle.color = themeColor[2]
  50 + // 水球颜色
  51 + props.chartConfig.option.series[0].color[0].colorStops = [
  52 + {
  53 + offset: 0,
  54 + color: themeColor[0]
  55 + },
  56 + {
  57 + offset: 1,
  58 + color: themeColor[1]
  59 + }
  60 + ]
  61 + }
  62 + option.value = props.chartConfig.option
  63 + } catch (error) {
  64 + console.log(error)
  65 + }
  66 + },
  67 + {
  68 + immediate: true
  69 + }
  70 +)
  71 +
  72 +// 数据处理
  73 +const dataHandle = (newData: number | string) => {
  74 + newData = isString(newData) ? parseFloat(newData) : newData
  75 + return parseFloat(newData.toFixed(2))
  76 +}
  77 +
  78 +// 编辑
  79 +watch(
  80 + () => props.chartConfig.option.dataset,
  81 + newData => {
  82 + if (!isString(newData) && !isNumber(newData)) return
  83 + props.chartConfig.option.series[0].data = [dataHandle(newData)]
  84 + option.value = props.chartConfig.option
  85 + },
  86 + {
  87 + immediate: true,
  88 + deep: false
  89 + }
  90 +)
  91 +
  92 +// 预览
  93 +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: number) => {
  94 + // @ts-ignore
  95 + option.value.series[0].data = [dataHandle(newData)]
  96 +})
  97 +</script>
... ...
  1 +import {WaterPoloConfig} from './WaterPolo/index'
  2 +
  3 +
  4 +export default [WaterPoloConfig]
... ...
  1 +export enum ChatCategoryEnum {
  2 + TWODANIMATION = 'TwoDAnimations',
  3 +
  4 + THREEDANIMATION = 'ThreeDAnimations',
  5 + TITLE = 'Titles',
  6 + MORE = 'Mores',
  7 +
  8 +}
  9 +
  10 +export enum ChatCategoryEnumName {
  11 + TITLE = '标题',
  12 + TWODANIMATION = '2D动画',
  13 + THREEDANIMATION = '3D动画',
  14 +
  15 + MORE = '更多',
  16 +}
... ...
  1 +import Mores from './Mores'
  2 +import TwoDAnimations from './TwoDAnimations'
  3 +import ThreeDAnimations from './ThreeDAnimations'
  4 +
  5 +export const ArtoonsList = [...TwoDAnimations, ...ThreeDAnimations, ...Mores,]
... ...
  1 +import { PublicConfigClass } from '@/packages/public'
  2 +import { CreateComponentType } from '@/packages/index.d'
  3 +import { LeftCenterRightHeadConfig } from './index'
  4 +import cloneDeep from 'lodash/cloneDeep'
  5 +
  6 +export const option = {
  7 + dataset: '我是渐变文本',
  8 + size: 20,
  9 + gradient: {
  10 + from: '#0000FFFF',
  11 + to: '#00FF00FF',
  12 + deg: 45
  13 + }
  14 +}
  15 +
  16 +export default class Config extends PublicConfigClass implements CreateComponentType {
  17 + public key = LeftCenterRightHeadConfig.key
  18 + public chartConfig = cloneDeep(LeftCenterRightHeadConfig)
  19 + public option = cloneDeep(option)
  20 +}
... ...
  1 +<template>
  2 + <collapse-item name="信息" :expanded="true">
  3 + <setting-item-box name="文字" :alone="true">
  4 + <setting-item>
  5 + <n-input v-model:value="optionData.dataset" size="small"></n-input>
  6 + </setting-item>
  7 + </setting-item-box>
  8 + </collapse-item>
  9 + <collapse-item name="样式" :expanded="true">
  10 + <setting-item-box name="文字">
  11 + <setting-item name="字体大小">
  12 + <n-input-number v-model:value="optionData.size" size="small" placeholder="字体大小"></n-input-number>
  13 + </setting-item>
  14 + </setting-item-box>
  15 + <setting-item-box name="渐变色参数">
  16 + <setting-item name="起始值">
  17 + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.from"></n-color-picker>
  18 + </setting-item>
  19 + <setting-item name="结束值">
  20 + <n-color-picker size="small" :modes="['hex']" v-model:value="optionData.gradient.to"></n-color-picker>
  21 + </setting-item>
  22 + <setting-item name="偏移角度">
  23 + <n-input-number v-model:value="optionData.gradient.deg" size="small" placeholder="颜色旋转"></n-input-number>
  24 + </setting-item>
  25 + </setting-item-box>
  26 +
  27 + </collapse-item>
  28 +</template>
  29 +
  30 +<script setup lang="ts">
  31 +import { PropType } from 'vue'
  32 +import { option } from './config'
  33 +import {
  34 + CollapseItem,
  35 + SettingItemBox,
  36 + SettingItem
  37 +} from '@/components/Pages/ChartItemSetting'
  38 +
  39 +const props = defineProps({
  40 + optionData: {
  41 + type: Object as PropType<typeof option>,
  42 + required: true
  43 + }
  44 +})
  45 +</script>
... ...
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
  3 +
  4 +export const LeftCenterRightHeadConfig: ConfigType = {
  5 + key: 'LeftCenterRightHead',
  6 + chartKey: 'VLeftCenterRightHead',
  7 + conKey: 'VCLeftCenterRightHead',
  8 + title: '左中右头部',
  9 + category: ChatCategoryEnum.HEADCOMBINATION,
  10 + categoryName: ChatCategoryEnumName.HEADCOMBINATION,
  11 + package: PackagesCategoryEnum.COMPOSES,
  12 + chartFrame: ChartFrameEnum.ECHARTS,
  13 + image: 'text_gradient.png'
  14 +}
... ...
  1 +<template>
  2 + <div class="go-text-box">
  3 + <n-gradient-text :size="size" :gradient="gradient">
  4 + {{ option.dataset }}
  5 + </n-gradient-text>
  6 + </div>
  7 +</template>
  8 +<script setup lang="ts">
  9 +import { PropType, toRefs, shallowReactive, watch } from 'vue'
  10 +import { CreateComponentType } from '@/packages/index.d'
  11 +import { useChartDataFetch } from '@/hooks'
  12 +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  13 +import { option as configOption } from './config'
  14 +
  15 +const props = defineProps({
  16 + chartConfig: {
  17 + type: Object as PropType<CreateComponentType>,
  18 + required: true
  19 + }
  20 +})
  21 +
  22 +const option = shallowReactive({
  23 + dataset: configOption.dataset
  24 +})
  25 +
  26 +const { w, h } = toRefs(props.chartConfig.attr)
  27 +const { size, gradient } = toRefs(props.chartConfig.option)
  28 +
  29 +watch(
  30 + () => props.chartConfig.option.dataset,
  31 + (newData: any) => {
  32 + option.dataset = newData
  33 + },
  34 + {
  35 + immediate: true,
  36 + deep: false
  37 + }
  38 +)
  39 +
  40 +useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
  41 + option.dataset = newData
  42 +})
  43 +</script>
  44 +
  45 +<style lang="scss" scoped>
  46 +@include go('text-box') {
  47 + display: flex;
  48 + align-items: center;
  49 + justify-content: center;
  50 + .n-gradient-text {
  51 + white-space: initial;
  52 + }
  53 +}
  54 +</style>
... ...
  1 +import { LeftCenterRightHeadConfig } from './LeftCenterRightHead/index'
  2 +
  3 +export default [LeftCenterRightHeadConfig]
... ...
  1 +import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
  2 +import { WordCloudConfig } from './index'
  3 +import { CreateComponentType } from '@/packages/index.d'
  4 +import cloneDeep from 'lodash/cloneDeep'
  5 +import dataJson from './data.json'
  6 +
  7 +export const includes = []
  8 +
  9 +export const ShapeEnumList = [
  10 + { label: '圆形', value: 'circle' },
  11 + { label: '心形', value: 'cardioid' },
  12 + { label: '钻石', value: 'diamond' },
  13 + { label: '右三角形', value: 'triangle-forward' },
  14 + { label: '三角形', value: 'triangle' },
  15 + { label: '五边形', value: 'pentagon' },
  16 + { label: '星星', value: 'star' }
  17 +]
  18 +
  19 +export const option = {
  20 + dataset: [...dataJson],
  21 + tooltip: {},
  22 + series: [
  23 + {
  24 + type: 'wordCloud',
  25 +
  26 + // “云”绘制的形状,可以是表示为回调函数,也可以是固定关键字。
  27 + // 可用值有:circle|cardioid|diamond|triangle-forward|triangle|pentagon|star
  28 + shape: 'circle',
  29 +
  30 + // 白色区域将被排除在绘制文本之外的剪影图像。
  31 + // 随着云的形状生长,形状选项将继续应用。
  32 + // maskImage: maskImage,
  33 +
  34 + // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud
  35 + // Default to be put in the center and has 75% x 80% size.
  36 + left: 'center',
  37 + top: 'center',
  38 + width: '70%',
  39 + height: '80%',
  40 + right: null,
  41 + bottom: null,
  42 +
  43 + // 文本大小范围,默认 [12,60]
  44 + sizeRange: [12, 60],
  45 +
  46 + // 文本旋转范围和程度的步骤。 文本将通过旋转步骤45在[-90,90]中随机旋转
  47 + rotationRange: [0, 0],
  48 + rotationStep: 0,
  49 +
  50 + // size of the grid in pixels for marking the availability of the canvas
  51 + // 网格大小越大,单词之间的差距就越大。
  52 + gridSize: 8,
  53 +
  54 + // 设置为true,以允许单词在画布之外部分地绘制。允许绘制大于画布的大小
  55 + drawOutOfBound: false,
  56 +
  57 + // If perform layout animation.
  58 + // NOTE disable it will lead to UI blocking when there is lots of words.
  59 + layoutAnimation: true,
  60 +
  61 + // Global text style
  62 + textStyle: {
  63 + fontFamily: 'sans-serif',
  64 + fontWeight: 'bold'
  65 + // 颜色可以是回调功能或颜色字符串
  66 + // color: function () {
  67 + // // 随机颜色
  68 + // return (
  69 + // 'rgb(' +
  70 + // [Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160)].join(
  71 + // ','
  72 + // ) +
  73 + // ')'
  74 + // )
  75 + // }
  76 + },
  77 + emphasis: {
  78 + focus: 'self',
  79 +
  80 + textStyle: {
  81 + shadowBlur: 10,
  82 + shadowColor: '#333'
  83 + }
  84 + },
  85 + data: [...dataJson]
  86 + }
  87 + ]
  88 +}
  89 +
  90 +export default class Config extends PublicConfigClass implements CreateComponentType {
  91 + public key = WordCloudConfig.key
  92 + public chartConfig = cloneDeep(WordCloudConfig)
  93 + // 图表配置项
  94 + public option = echartOptionProfixHandle(option, includes)
  95 +}
... ...
  1 +<template>
  2 + <collapse-item name="词云" expanded>
  3 + <setting-item-box name="形状">
  4 + <setting-item>
  5 + <n-select v-model:value="optionData.series[0].shape" size="small" :options="ShapeEnumList" />
  6 + </setting-item>
  7 + <setting-item>
  8 + <n-checkbox v-model:checked="optionData.series[0].drawOutOfBound" size="small">允许出边</n-checkbox>
  9 + </setting-item>
  10 + </setting-item-box>
  11 +
  12 + <setting-item-box name="布局">
  13 + <setting-item name="宽度">
  14 + <n-slider
  15 + v-model:value="series.width"
  16 + :min="0"
  17 + :max="100"
  18 + :format-tooltip="sliderFormatTooltip"
  19 + @update:value="updateWidth"
  20 + ></n-slider>
  21 + </setting-item>
  22 + <setting-item name="高度">
  23 + <n-slider
  24 + v-model:value="series.height"
  25 + :min="0"
  26 + :max="100"
  27 + :format-tooltip="sliderFormatTooltip"
  28 + @update:value="updateHeight"
  29 + ></n-slider>
  30 + </setting-item>
  31 + </setting-item-box>
  32 +
  33 + <setting-item-box name="样式" alone>
  34 + <setting-item name="字体区间(最小/最大字体)">
  35 + <n-slider v-model:value="optionData.series[0].sizeRange" range :step="1" :min="6" :max="100" />
  36 + </setting-item>
  37 + <setting-item name="旋转角度">
  38 + <n-slider v-model:value="series.rotationStep" :step="15" :min="0" :max="45" @update:value="updateRotation" />
  39 + </setting-item>
  40 + </setting-item-box>
  41 + </collapse-item>
  42 +</template>
  43 +
  44 +<script setup lang="ts">
  45 +import { PropType, computed } from 'vue'
  46 +import { option, ShapeEnumList } from './config'
  47 +// eslint-disable-next-line no-unused-vars
  48 +import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
  49 +
  50 +const props = defineProps({
  51 + optionData: {
  52 + type: Object as PropType<typeof option>,
  53 + required: true
  54 + }
  55 +})
  56 +
  57 +const series = computed(() => {
  58 + const { width, height, rotationStep } = props.optionData.series[0]
  59 + return {
  60 + width: +width.replace('%', ''),
  61 + height: +height.replace('%', ''),
  62 + rotationStep
  63 + }
  64 +})
  65 +
  66 +const sliderFormatTooltip = (v: number) => {
  67 + return `${v}%`
  68 +}
  69 +
  70 +const updateWidth = (value: number) => {
  71 + props.optionData.series[0].width = `${value}%`
  72 +}
  73 +
  74 +const updateHeight = (value: number) => {
  75 + props.optionData.series[0].height = `${value}%`
  76 +}
  77 +
  78 +const updateRotation = (value: number) => {
  79 + props.optionData.series[0].rotationStep = value
  80 + props.optionData.series[0].rotationRange = value === 0 ? [0, 0] : [-90, 90]
  81 +}
  82 +</script>
... ...
  1 +[
  2 + {
  3 + "name": "数据可视化",
  4 + "value": 8000,
  5 + "textStyle": {
  6 + "color": "#78fbb2"
  7 + },
  8 + "emphasis": {
  9 + "textStyle": {
  10 + "color": "red"
  11 + }
  12 + }
  13 + },
  14 + {
  15 + "name": "GO VIEW",
  16 + "value": 6181
  17 + },
  18 + {
  19 + "name": "低代码",
  20 + "value": 4386
  21 + },
  22 + {
  23 + "name": "Vue3",
  24 + "value": 4055
  25 + },
  26 + {
  27 + "name": "TypeScript4",
  28 + "value": 2467
  29 + },
  30 + {
  31 + "name": "Vite2",
  32 + "value": 2244
  33 + },
  34 + {
  35 + "name": "NaiveUI",
  36 + "value": 1898
  37 + },
  38 + {
  39 + "name": "ECharts5",
  40 + "value": 1484
  41 + },
  42 + {
  43 + "name": "Axios",
  44 + "value": 1112
  45 + },
  46 + {
  47 + "name": "Pinia2",
  48 + "value": 965
  49 + },
  50 + {
  51 + "name": "PlopJS",
  52 + "value": 847
  53 + },
  54 + {
  55 + "name": "sfc",
  56 + "value": 582
  57 + },
  58 + {
  59 + "name": "SCSS",
  60 + "value": 555
  61 + },
  62 + {
  63 + "name": "pnpm",
  64 + "value": 550
  65 + },
  66 + {
  67 + "name": "eslint",
  68 + "value": 462
  69 + },
  70 + {
  71 + "name": "json",
  72 + "value": 366
  73 + },
  74 + {
  75 + "name": "图表",
  76 + "value": 360
  77 + },
  78 + {
  79 + "name": "地图",
  80 + "value": 282
  81 + },
  82 + {
  83 + "name": "时钟",
  84 + "value": 273
  85 + },
  86 + {
  87 + "name": "标题",
  88 + "value": 265
  89 + }
  90 +]
... ...
  1 +import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
  2 +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
  3 +
  4 +export const WordCloudConfig: ConfigType = {
  5 + key: 'WordCloud',
  6 + chartKey: 'VWordCloud',
  7 + conKey: 'VCWordCloud',
  8 + title: '词云',
  9 + category: ChatCategoryEnum.MORE,
  10 + categoryName: ChatCategoryEnumName.MORE,
  11 + package: PackagesCategoryEnum.INFORMATIONS,
  12 + chartFrame: ChartFrameEnum.COMMON,
  13 + image: 'words_cloud.png'
  14 +}
... ...
  1 +<template>
  2 + <v-chart
  3 + ref="vChartRef"
  4 + :theme="themeColor"
  5 + :option="option"
  6 + :manual-update="isPreview()"
  7 + :update-options="{ replaceMerge: replaceMergeArr }"
  8 + autoresize
  9 + ></v-chart>
  10 +</template>
  11 +
  12 +<script setup lang="ts">
  13 +import { ref, computed, watch, PropType } from 'vue'
  14 +import VChart from 'vue-echarts'
  15 +import 'echarts-wordcloud'
  16 +import { use } from 'echarts/core'
  17 +import { CanvasRenderer } from 'echarts/renderers'
  18 +import config, { includes } from './config'
  19 +import { mergeTheme, setOption } from '@/packages/public/chart'
  20 +import { useChartDataFetch } from '@/hooks'
  21 +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  22 +import { isPreview } from '@/utils'
  23 +import { GridComponent, TooltipComponent } from 'echarts/components'
  24 +import dataJson from './data.json'
  25 +
  26 +const props = defineProps({
  27 + themeSetting: {
  28 + type: Object,
  29 + required: true
  30 + },
  31 + themeColor: {
  32 + type: Object,
  33 + required: true
  34 + },
  35 + chartConfig: {
  36 + type: Object as PropType<config>,
  37 + required: true
  38 + }
  39 +})
  40 +
  41 +use([CanvasRenderer, GridComponent, TooltipComponent])
  42 +
  43 +const replaceMergeArr = ref<string[]>()
  44 +
  45 +const option = computed(() => {
  46 + return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
  47 +})
  48 +
  49 +const dataSetHandle = (dataset: typeof dataJson) => {
  50 + try {
  51 + dataset && (props.chartConfig.option.series[0].data = dataset)
  52 + vChartRef.value && isPreview() && setOption(vChartRef.value, props.chartConfig.option)
  53 + } catch (error) {
  54 + console.log(error)
  55 + }
  56 +}
  57 +
  58 +// dataset 无法变更条数的补丁
  59 +watch(
  60 + () => props.chartConfig.option.dataset,
  61 + newData => {
  62 + dataSetHandle(newData)
  63 + },
  64 + {
  65 + deep: false
  66 + }
  67 +)
  68 +
  69 +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => {
  70 + dataSetHandle(newData)
  71 +})
  72 +</script>
... ...
  1 +import { WordCloudConfig } from './WordCloud/index'
  2 +
  3 +export default [WordCloudConfig]
... ...
  1 +export enum ChatCategoryEnum {
  2 + TITLE = 'Titles',
  3 + HEADCOMBINATION = 'HeadCombinations',
  4 + MORE = 'Mores'
  5 +}
  6 +
  7 +export enum ChatCategoryEnumName {
  8 + TITLE = '标题',
  9 + HEADCOMBINATION = '头部组合',
  10 + MORE = '更多'
  11 +}
... ...
  1 +import HeadCombinations from './HeadCombinations'
  2 +import Mores from './Mores'
  3 +
  4 +export const ComposesList = [...HeadCombinations, ...Mores]
... ...
... ... @@ -5,6 +5,7 @@
5 5 v-show="hidePackageOneCategory"
6 6 class="chart-menu-width"
7 7 v-model:value="selectValue"
  8 + style="width: 80px;"
8 9 :options="packages.menuOptions"
9 10 :icon-size="16"
10 11 :indent="18"
... ...