Merge branch 'dev' of http://119.45.132.149:3000/security/go-view-fetch
This commit is contained in:
commit
713715028b
79
src/packages/components/Charts/Bars/Bar3d/config.ts
Normal file
79
src/packages/components/Charts/Bars/Bar3d/config.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { Bar3DConfig } from './index'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid', 'dataZoom']
|
||||
export const seriesItem = {
|
||||
type: 'bar',
|
||||
barWidth: 19,
|
||||
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 1,
|
||||
x2: 0,
|
||||
y2: 0,
|
||||
colorStops: [
|
||||
{ offset: 0, color: '#01c5ff' }, // 底部颜色(深蓝)
|
||||
{ offset: 1, color: '#fbff01ff' } // 顶部颜色(浅蓝)
|
||||
]
|
||||
},
|
||||
borderRadius: 2
|
||||
},
|
||||
|
||||
}
|
||||
export const option = {
|
||||
tooltip: {
|
||||
show: false,
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
show: false,
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false // 新增或修改这行,隐藏图例(即“当日作业情况”文字和旁边的小点)
|
||||
},
|
||||
xAxis: {
|
||||
show: false,
|
||||
type: 'category'
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 25
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
show: false,
|
||||
height: 15,
|
||||
start: 0,
|
||||
end: 100
|
||||
},
|
||||
{
|
||||
show: false,
|
||||
yAxisIndex: 0,
|
||||
filterMode: 'empty',
|
||||
width: 15,
|
||||
height: 200,
|
||||
showDataShadow: false,
|
||||
left: '93%',
|
||||
start: 0,
|
||||
end: 100
|
||||
},
|
||||
],
|
||||
dataset: { ...dataJson },
|
||||
series: [seriesItem]
|
||||
}
|
||||
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = Bar3DConfig.key
|
||||
public chartConfig = cloneDeep(Bar3DConfig)
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
}
|
82
src/packages/components/Charts/Bars/Bar3d/config.vue
Normal file
82
src/packages/components/Charts/Bars/Bar3d/config.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<!-- Echarts 全局设置 -->
|
||||
<global-setting :optionData="optionData"></global-setting>
|
||||
<!-- <CollapseItem name="额外刻度设置" :expanded="true">
|
||||
<SettingItemBox name="Y轴最小值">
|
||||
<n-input-number v-model:value="yAxisData?.min" :min="0" size="small"></n-input-number>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="Y轴最大值">
|
||||
<n-input-number v-model:value="yAxisData?.max" :min="0" size="small"></n-input-number>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="Y轴步长">
|
||||
<n-input-number v-model:value="yAxisData?.interval" :min="1" size="small"></n-input-number>
|
||||
</SettingItemBox>
|
||||
</CollapseItem> -->
|
||||
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`柱状图-${index + 1}`" :expanded="true">
|
||||
<SettingItemBox name="图形">
|
||||
<SettingItem name="宽度">
|
||||
<n-input-number
|
||||
v-model:value="item.barWidth"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
placeholder="自动计算"
|
||||
></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="圆角">
|
||||
<n-input-number v-model:value="item.itemStyle.borderRadius" :min="0" size="small"></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="底部颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color.colorStops[0].color"></n-color-picker>
|
||||
</SettingItem>
|
||||
<SettingItem name="顶部颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.itemStyle.color.colorStops[1].color"></n-color-picker>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select
|
||||
v-model:value="item.label.position"
|
||||
:options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' }
|
||||
]"
|
||||
/>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BaseTransitionPropsValidators, PropType, computed } from 'vue'
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<GlobalThemeJsonType>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const seriesList = computed(() => {
|
||||
return props.optionData.series
|
||||
})
|
||||
const yAxisData = computed(() => {
|
||||
return props.optionData.yAxis
|
||||
})
|
||||
</script>
|
9
src/packages/components/Charts/Bars/Bar3d/data.json
Normal file
9
src/packages/components/Charts/Bars/Bar3d/data.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"dimensions": ["场所分布情况", "当日作业分布情况"],
|
||||
"source": [
|
||||
{
|
||||
"场所分布情况": 150,
|
||||
"当日作业分布情况": 75
|
||||
}
|
||||
]
|
||||
}
|
14
src/packages/components/Charts/Bars/Bar3d/index.ts
Normal file
14
src/packages/components/Charts/Bars/Bar3d/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
|
||||
|
||||
export const Bar3DConfig: ConfigType = {
|
||||
key: 'Bar3D',
|
||||
chartKey: 'VBar3D',
|
||||
conKey: 'VCBar3D',
|
||||
title: '3D柱状图',
|
||||
category: ChatCategoryEnum.BAR,
|
||||
categoryName: ChatCategoryEnumName.BAR,
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: 'bar_x.png'
|
||||
}
|
97
src/packages/components/Charts/Bars/Bar3d/index.vue
Normal file
97
src/packages/components/Charts/Bars/Bar3d/index.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<v-chart
|
||||
ref="vChartRef"
|
||||
:init-options="initOptions"
|
||||
:theme="themeColor"
|
||||
:option="option"
|
||||
:update-options="{
|
||||
replaceMerge: replaceMergeArr
|
||||
}"
|
||||
autoresize
|
||||
></v-chart>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, computed, watch, PropType } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { BarChart } from 'echarts/charts'
|
||||
import config, { includes, seriesItem } from './config'
|
||||
import { mergeTheme } 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'
|
||||
import isObject from 'lodash/isObject'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
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, BarChart, GridComponent, TooltipComponent, LegendComponent])
|
||||
|
||||
const replaceMergeArr = ref<string[]>()
|
||||
|
||||
const option = computed(() => {
|
||||
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
})
|
||||
|
||||
// dataset 无法变更条数的补丁
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: { dimensions: any }, oldData) => {
|
||||
try {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (Array.isArray(newData?.dimensions)) {
|
||||
const seriesArr = []
|
||||
// 对oldData进行判断,防止传入错误数据之后对旧维度判断产生干扰
|
||||
// 此处计算的是dimensions的Y轴维度,若是dimensions.length为0或1,则默认为1,排除X轴维度干扰
|
||||
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) {
|
||||
if(!oldData || !oldData?.dimensions || !Array.isArray(oldData?.dimensions) || !oldData?.dimensions.length ) {
|
||||
props.chartConfig.option.series=[]
|
||||
}
|
||||
for (let i = 0; i < dimensionsGap; i++) {
|
||||
seriesArr.push(cloneDeep(seriesItem))
|
||||
}
|
||||
props.chartConfig.option.series.push(...seriesArr)
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
props.chartConfig.option.dataset = newData
|
||||
})
|
||||
</script>
|
@ -7,6 +7,7 @@ import { BarRankConfig } from './BarRank'
|
||||
import { BarRank2Config } from './BarRank2'
|
||||
import { BarRank3Config } from './BarRank3'
|
||||
import { BarLinearConfig } from './BarLinear/index'
|
||||
import { Bar3DConfig } from './Bar3d/index'
|
||||
|
||||
export default [
|
||||
BarCommonConfig,
|
||||
@ -17,5 +18,6 @@ export default [
|
||||
BarRankConfig,
|
||||
BarRank2Config,
|
||||
BarRank3Config,
|
||||
BarLinearConfig
|
||||
BarLinearConfig,
|
||||
Bar3DConfig
|
||||
]
|
||||
|
@ -0,0 +1,114 @@
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { LineDropdownConfig } from './index'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['xAxis', 'yAxis', 'grid']
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'rgba(13, 16, 22, 1)',
|
||||
grid: {
|
||||
left: '8%',
|
||||
right: '8%',
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: 12
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: 'rgba(58, 160, 255, 1)',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
dataset: { ...dataJson },
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none', // 默认不显示点
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 1,
|
||||
y2: 0,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(127, 216, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
symbol: 'circle', // 鼠标悬停时显示圆点
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: 'rgba(58, 160, 255, 1)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.8)',
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0.5)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 0.05)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key: string = LineDropdownConfig.key
|
||||
public chartConfig = cloneDeep(LineDropdownConfig)
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { NSpace, NCard, NFormItem, NInput } from 'naive-ui'
|
||||
import config from './config'
|
||||
|
||||
defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<config>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"dimensions": ["time", "value"],
|
||||
"source": [
|
||||
{
|
||||
"time": "13:00",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"time": "13:05",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"time": "13:10",
|
||||
"value": 55
|
||||
},
|
||||
{
|
||||
"time": "13:15",
|
||||
"value": 48
|
||||
},
|
||||
{
|
||||
"time": "13:20",
|
||||
"value": 42
|
||||
},
|
||||
{
|
||||
"time": "13:25",
|
||||
"value": 38
|
||||
},
|
||||
{
|
||||
"time": "13:30",
|
||||
"value": 35
|
||||
},
|
||||
{
|
||||
"time": "13:35",
|
||||
"value": 40
|
||||
},
|
||||
{
|
||||
"time": "13:40",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"time": "13:45",
|
||||
"value": 65
|
||||
},
|
||||
{
|
||||
"time": "13:50",
|
||||
"value": 78
|
||||
},
|
||||
{
|
||||
"time": "13:55",
|
||||
"value": 80
|
||||
},
|
||||
{
|
||||
"time": "14:00",
|
||||
"value": 75
|
||||
},
|
||||
{
|
||||
"time": "14:05",
|
||||
"value": 68
|
||||
},
|
||||
{
|
||||
"time": "14:10",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"time": "14:15",
|
||||
"value": 55
|
||||
},
|
||||
{
|
||||
"time": "14:20",
|
||||
"value": 52
|
||||
},
|
||||
{
|
||||
"time": "14:25",
|
||||
"value": 58
|
||||
},
|
||||
{
|
||||
"time": "14:30",
|
||||
"value": 62
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../..'
|
||||
|
||||
export const LineDropdownConfig: ConfigType = {
|
||||
key: 'LineDropdown_ConfinedSpace',
|
||||
chartKey: 'VLineDropdown_ConfinedSpace',
|
||||
conKey: 'VCLineDropdown_ConfinedSpace',
|
||||
title: '下拉折线图',
|
||||
category: 'ConfinedSpace',
|
||||
categoryName: '有限空间组件',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: 'line_warn.png'
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
||||
import {MapConfig} from './Map'
|
||||
import {PieCircleCommenConfig} from './PieCircleCommen'
|
||||
import {AlarmNowListConfig} from './AlarmNowList'
|
||||
export default [MapConfig,PieCircleCommenConfig,AlarmNowListConfig]
|
||||
import { LineDropdownConfig } from './LineDropdown/index'
|
||||
export default [MapConfig,LineDropdownConfig,PieCircleCommenConfig,AlarmNowListConfig]
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { AlarmListConfig } from './index'
|
||||
import dataJson from './data.json'
|
||||
export enum FontWeightEnum {
|
||||
NORMAL = '常规',
|
||||
BOLD = '加粗',
|
||||
}
|
||||
|
||||
export enum FontStyleEnum {
|
||||
NORMAL = '常规',
|
||||
ITALIC = '斜体',
|
||||
}
|
||||
|
||||
export const FontWeightObject = {
|
||||
[FontWeightEnum.NORMAL]: 'normal',
|
||||
[FontWeightEnum.BOLD]: 'bold',
|
||||
}
|
||||
|
||||
export const FontStyleObject = {
|
||||
[FontStyleEnum.NORMAL]: 'normal',
|
||||
[FontStyleEnum.ITALIC]: 'italic',
|
||||
}
|
||||
export const option = {
|
||||
dataset: dataJson.source,
|
||||
header: ['报警事件', '时间', '所属企业', '状态'],
|
||||
headerTextColor: '#B4B4B4',
|
||||
textColor: '#ffffff',
|
||||
headerBackgroundColor: '#17325F',
|
||||
itemBackgroundColor: '#242834',
|
||||
statusColors: {
|
||||
'未解决': '#FF4D4F',
|
||||
'已解决': '#ffffff'
|
||||
},
|
||||
headerHeight: 28,
|
||||
itemHeight: 28,
|
||||
fontSize: 12,
|
||||
title: '近60分钟报警信息',
|
||||
titleSize: 17,
|
||||
titleColor: '#ffffff',
|
||||
iconColor: '#00E5FF',
|
||||
fontWeight: 'normal',
|
||||
fontStyle: 'normal',
|
||||
paddingX: 40,
|
||||
paddingY: -30,
|
||||
letterSpacing: 1,
|
||||
isShowButton: false
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = AlarmListConfig.key
|
||||
public chartConfig = AlarmListConfig
|
||||
public option = option
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<collapse-item name="信息" :expanded="true">
|
||||
<setting-item-box name="标题" :alone="true">
|
||||
<setting-item>
|
||||
<n-input v-model:value="optionData.title" type="textarea" size="small"></n-input>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
|
||||
<collapse-item name="样式" :expanded="true">
|
||||
<setting-item-box name="标题">
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.titleColor"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="字体大小">
|
||||
<n-input-number v-model:value="optionData.titleSize" size="small" placeholder="字体大小"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="字体粗细">
|
||||
<n-select v-model:value="optionData.fontWeight" size="small" :options="fontWeightOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="字体风格">
|
||||
<n-select v-model:value="optionData.fontStyle" size="small" :options="fontStyleOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="X轴内边距">
|
||||
<n-input-number v-model:value="optionData.paddingX" size="small" placeholder="输入内边距"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="Y轴内边距">
|
||||
<n-input-number v-model:value="optionData.paddingY" size="small" placeholder="输入内边距"></n-input-number>
|
||||
</setting-item>
|
||||
|
||||
|
||||
<setting-item name="字间距">
|
||||
<n-input-number v-model:value="optionData.letterSpacing" size="small" placeholder="输入字间距"></n-input-number>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { option, FontWeightEnum, FontWeightObject, FontStyleEnum, FontStyleObject } from './config'
|
||||
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const fontWeightOptions = [
|
||||
{
|
||||
label: FontWeightEnum.NORMAL,
|
||||
value: FontWeightObject[FontWeightEnum.NORMAL]
|
||||
},
|
||||
{
|
||||
label: FontWeightEnum.BOLD,
|
||||
value: FontWeightObject[FontWeightEnum.BOLD]
|
||||
}
|
||||
]
|
||||
const fontStyleOptions = [
|
||||
{
|
||||
label: FontStyleEnum.NORMAL,
|
||||
value: FontStyleObject[FontStyleEnum.NORMAL]
|
||||
},
|
||||
{
|
||||
label: FontStyleEnum.ITALIC,
|
||||
value: FontStyleObject[FontStyleEnum.ITALIC]
|
||||
}
|
||||
]
|
||||
</script>
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"source": [
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "未解决"
|
||||
},
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "已解决"
|
||||
},
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "未解决"
|
||||
},
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "未解决"
|
||||
},
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "未解决"
|
||||
},
|
||||
{
|
||||
"event": "报警报警报警报警",
|
||||
"time": "10:13:24",
|
||||
"enterprise": "企业简称企业简称",
|
||||
"status": "未解决"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
|
||||
|
||||
export const AlarmListConfig: ConfigType = {
|
||||
key: 'AlarmListHaz',
|
||||
chartKey: 'VAlarmListHaz',
|
||||
conKey: 'VCAlarmListHaz',
|
||||
title: '报警列表',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.COMMON,
|
||||
image: 'alarm_list.png'
|
||||
}
|
||||
export default AlarmListConfig
|
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<SmallBorder class="SmallBorder">
|
||||
<div class="go-alarm-list">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
|
||||
<div class="title-text">
|
||||
<span :style="{
|
||||
color: option.titleColor,
|
||||
fontSize: option.titleSize + 'px',
|
||||
fontWeight: option.fontWeight,
|
||||
fontStyle: option.fontStyle,
|
||||
marginLeft: option.paddingX + 'px',
|
||||
marginTop: option.paddingY + 'px',
|
||||
letterSpacing: option.letterSpacing + 'px'
|
||||
}">{{ option.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="option.isShowButton" class="header-right-squares">
|
||||
<div class="square" style="background-color: #2e69e0"></div>
|
||||
<div class="square" style="background-color: #2e9bf0"></div>
|
||||
<div class="square" style="background-color: #2ef0b3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-header" :style="{
|
||||
height: option.headerHeight + 'px'
|
||||
}">
|
||||
<div v-for="header in option.header" :key="header" class="header-item"
|
||||
:style="{ color: option.headerTextColor, fontSize: option.fontSize + 'px' }">
|
||||
{{ header }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<div v-for="(item, index) in option.dataset" :key="index" class="table-row"
|
||||
:style="{ backgroundColor: option.itemBackgroundColor, height: option.itemHeight + 'px' }">
|
||||
<div class="row-item" :style="{ color: option.textColor, fontSize: option.fontSize + 'px' }">
|
||||
{{ item.event }}
|
||||
</div>
|
||||
<div class="row-item" :style="{ color: option.textColor, fontSize: option.fontSize + 'px' }">
|
||||
{{ item.time }}
|
||||
</div>
|
||||
<div class="row-item" :style="{ color: option.textColor, fontSize: option.fontSize + 'px' }">
|
||||
{{ item.enterprise }}
|
||||
</div>
|
||||
<div class="row-item" :style="{ color: getStatusColor(item.status), fontSize: option.fontSize + 'px' }">
|
||||
{{ item.status }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SmallBorder>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed } from 'vue'
|
||||
import { option as configOption } from './config'
|
||||
import SmallBorder from '../SmallBorder/index.vue'
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<{ option: typeof configOption }>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const option = computed(() => props.chartConfig.option)
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
return props.chartConfig.option.statusColors[status] || '#ffffff'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.SmallBorder {
|
||||
position: relative;
|
||||
// background-color: #ffffff;
|
||||
}
|
||||
|
||||
.go-alarm-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #ffffff;
|
||||
padding: 12px;
|
||||
margin-top: 5px;
|
||||
box-sizing: border-box;
|
||||
// background-color: #4da6ff;
|
||||
z-index: 10;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title-text {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right-squares {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.square {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
background: linear-gradient(to top, #226493c8, #0f2448);
|
||||
border-bottom: 2px solid #4da6ff;
|
||||
margin-top: 10px;
|
||||
|
||||
.header-item {
|
||||
flex: 1 0 25%;
|
||||
margin-right: 1.5em;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.table-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* 隐藏滚动条 */
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
scrollbar-width: none;
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.row-item {
|
||||
flex: 1 0 25%;
|
||||
margin-right: 1.5em;
|
||||
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 174 KiB |
@ -0,0 +1,145 @@
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { LineDropdownConfig } from './index'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['xAxis', 'yAxis', 'grid']
|
||||
|
||||
// 其它配置
|
||||
const otherConfig = {
|
||||
dateTime: {
|
||||
selectValue: 'day',
|
||||
dataset: [
|
||||
{
|
||||
label: '当天',
|
||||
value: 'day'
|
||||
},
|
||||
{
|
||||
label: '本周',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '当月',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '本季度',
|
||||
value: 'quarter'
|
||||
},
|
||||
{
|
||||
label: '当年',
|
||||
value: 'year'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const option = {
|
||||
...otherConfig,
|
||||
backgroundColor: 'rgba(13, 16, 22, 1)',
|
||||
grid: {
|
||||
left: '8%',
|
||||
right: '8%',
|
||||
top: '15%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: 12
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
fontSize: 12
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: 'rgba(58, 160, 255, 1)',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
},
|
||||
dataset: { ...dataJson },
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none', // 默认不显示点
|
||||
symbolSize: 8,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 1,
|
||||
y2: 0,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 1)' },
|
||||
{ offset: 1, color: 'rgba(127, 216, 255, 1)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
symbol: 'circle', // 鼠标悬停时显示圆点
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: 'rgba(58, 160, 255, 1)',
|
||||
borderColor: 'rgba(255, 255, 255, 0.8)',
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0.5)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 0.05)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key: string = LineDropdownConfig.key
|
||||
public chartConfig = cloneDeep(LineDropdownConfig)
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { NSpace, NCard, NFormItem, NInput } from 'naive-ui'
|
||||
import config from './config'
|
||||
|
||||
defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<config>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"dimensions": ["time", "value"],
|
||||
"source": [
|
||||
{
|
||||
"time": "13:00",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"time": "13:05",
|
||||
"value": 45
|
||||
},
|
||||
{
|
||||
"time": "13:10",
|
||||
"value": 55
|
||||
},
|
||||
{
|
||||
"time": "13:15",
|
||||
"value": 48
|
||||
},
|
||||
{
|
||||
"time": "13:20",
|
||||
"value": 42
|
||||
},
|
||||
{
|
||||
"time": "13:25",
|
||||
"value": 38
|
||||
},
|
||||
{
|
||||
"time": "13:30",
|
||||
"value": 35
|
||||
},
|
||||
{
|
||||
"time": "13:35",
|
||||
"value": 40
|
||||
},
|
||||
{
|
||||
"time": "13:40",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"time": "13:45",
|
||||
"value": 65
|
||||
},
|
||||
{
|
||||
"time": "13:50",
|
||||
"value": 78
|
||||
},
|
||||
{
|
||||
"time": "13:55",
|
||||
"value": 80
|
||||
},
|
||||
{
|
||||
"time": "14:00",
|
||||
"value": 75
|
||||
},
|
||||
{
|
||||
"time": "14:05",
|
||||
"value": 68
|
||||
},
|
||||
{
|
||||
"time": "14:10",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"time": "14:15",
|
||||
"value": 55
|
||||
},
|
||||
{
|
||||
"time": "14:20",
|
||||
"value": 52
|
||||
},
|
||||
{
|
||||
"time": "14:25",
|
||||
"value": 58
|
||||
},
|
||||
{
|
||||
"time": "14:30",
|
||||
"value": 62
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../..'
|
||||
|
||||
export const LineDropdownConfig: ConfigType = {
|
||||
key: 'LineDropdownHaz',
|
||||
chartKey: 'VLineDropdownHaz',
|
||||
conKey: 'VCLineDropdownHaz',
|
||||
title: '下拉折线图',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: 'line_warn.png'
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="go-border-box">
|
||||
<img src="./assets/title.svg" class="svg" />
|
||||
<div class="header-title">报警趋势</div>
|
||||
<CustomSelect
|
||||
:options="option.dateTime.dataset"
|
||||
:selectedValue="option.dateTime.selectValue"
|
||||
@change="handleSelectChange"
|
||||
/>
|
||||
<div class="line-dropdown-container">
|
||||
<!-- 下拉框 -->
|
||||
<!-- <div class="dropdown-container">
|
||||
<select v-model="selectedOption" @change="handleDropdownChange" class="custom-dropdown1">
|
||||
<option value="总数">总数</option>
|
||||
<option value="类型1">类型1</option>
|
||||
<option value="类型2">类型2</option>
|
||||
<option value="类型3">类型3</option>
|
||||
</select>
|
||||
<div class="dropdown-arrow">▼</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 折线图 -->
|
||||
<div class="chart-container">
|
||||
<v-chart ref="vChartRef" autoresize :init-options="initOptions" :theme="themeColor" :option="option"></v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType, ref, 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 { LineChart } from 'echarts/charts'
|
||||
import { mergeTheme } from '@/packages/public/chart'
|
||||
import config, { includes } from './config'
|
||||
import { DatasetComponent, GridComponent, TooltipComponent } from 'echarts/components'
|
||||
import CustomSelect from './select.vue'
|
||||
|
||||
const props = defineProps({
|
||||
themeSetting: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
themeColor: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
chartConfig: {
|
||||
type: Object as PropType<config>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const selectedOption = ref('总数')
|
||||
|
||||
const initOptions = useCanvasInitOptions(props.chartConfig.option, props.themeSetting)
|
||||
|
||||
use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent])
|
||||
|
||||
const option = computed(() => {
|
||||
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
})
|
||||
|
||||
const handleDropdownChange = () => {
|
||||
// 这里可以根据选择的选项更新图表数据
|
||||
console.log('Selected option:', selectedOption.value)
|
||||
}
|
||||
|
||||
// 处理下拉选择器变化
|
||||
const handleSelectChange = (value: any) => {
|
||||
props.chartConfig.option.dateTime.selectValue = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-dropdown-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(13, 16, 22, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 7%;
|
||||
width: 86%;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
/* 添加边框样式到父容器 */
|
||||
border-radius: 8px;
|
||||
padding: 1px;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0.8) 0%,
|
||||
rgba(200, 200, 200, 0.6) 25%,
|
||||
rgba(150, 150, 150, 0.5) 50%,
|
||||
rgba(100, 100, 100, 0.4) 75%,
|
||||
rgba(60, 60, 60, 0.3) 100%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* hover状态的彩色渐变边框 */
|
||||
.dropdown-container:hover {
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(58, 160, 255, 1) 0%,
|
||||
rgba(98, 255, 198, 0.8) 25%,
|
||||
rgba(54, 110, 255, 1) 50%,
|
||||
rgba(28, 234, 160, 0.8) 75%,
|
||||
rgba(58, 160, 255, 1) 100%);
|
||||
}
|
||||
|
||||
/* focus状态的增强彩色渐变边框 */
|
||||
.dropdown-container:focus-within {
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(58, 160, 255, 1) 0%,
|
||||
rgba(98, 255, 198, 1) 25%,
|
||||
rgba(54, 110, 255, 1) 50%,
|
||||
rgba(28, 234, 160, 1) 75%,
|
||||
rgba(58, 160, 255, 1) 100%);
|
||||
}
|
||||
|
||||
.custom-dropdown1 {
|
||||
/* 渐变背景 */
|
||||
background: linear-gradient(135deg,
|
||||
rgba(25, 35, 45, 0.95) 0%,
|
||||
rgba(15, 25, 35, 0.95) 50%,
|
||||
rgba(35, 45, 55, 0.95) 100%);
|
||||
|
||||
/* 移除边框,因为已经移到父容器 */
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
padding: 10px 35px 10px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
cursor: pointer;
|
||||
min-width: 120px;
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.custom-dropdown1:hover {
|
||||
box-shadow: 0 6px 20px rgba(58, 160, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
/* 增强背景渐变 */
|
||||
background: linear-gradient(135deg,
|
||||
rgba(35, 45, 55, 0.95) 0%,
|
||||
rgba(25, 35, 45, 0.95) 50%,
|
||||
rgba(45, 55, 65, 0.95) 100%);
|
||||
}
|
||||
|
||||
.custom-dropdown1:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(58, 160, 255, 0.1), 0 6px 20px rgba(58, 160, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 优化下拉选项样式 - 参考Ant Design */
|
||||
.custom-dropdown1 option {
|
||||
background: rgba(30, 40, 50, 0.98);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
/* 添加细微的边框分隔 */
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.custom-dropdown1 option:hover {
|
||||
background: rgba(58, 160, 255, 0.15);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.custom-dropdown1 option:checked,
|
||||
.custom-dropdown1 option:selected {
|
||||
background: rgba(58, 160, 255, 0.25);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.custom-dropdown1 option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 为不同浏览器优化下拉选项样式 */
|
||||
.custom-dropdown1 optgroup {
|
||||
background: rgba(20, 30, 40, 1);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
padding: 8px 16px 4px 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* 添加下拉箭头动画 */
|
||||
.dropdown-arrow {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #ffffff;
|
||||
pointer-events: none;
|
||||
font-size: 12px;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.dropdown-container:hover .dropdown-arrow {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-50%) rotate(180deg);
|
||||
}
|
||||
|
||||
.custom-dropdown1:focus + .dropdown-arrow {
|
||||
transform: translateY(-50%) rotate(180deg);
|
||||
color: rgba(58, 160, 255, 1);
|
||||
text-shadow: 0 0 8px rgba(58, 160, 255, 0.5);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
padding-top: 0px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@include go(border-box) {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
// 不渐变
|
||||
// background-color: #0E121B;
|
||||
// 渐变
|
||||
background: linear-gradient(to top,
|
||||
rgba(14, 18, 27, 1) 0%,
|
||||
rgba(14, 18, 27, 0.6) 100%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
/* 边框宽度 */
|
||||
background: linear-gradient(to top,
|
||||
rgba(128, 128, 128, 0.3),
|
||||
rgba(128, 128, 128, 0));
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
left: 80px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #eee;
|
||||
font-style: italic;
|
||||
text-shadow: 0 0 10px #00E5FF;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-title{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
left: 80px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #eee;
|
||||
font-style: italic;
|
||||
text-shadow: 0 0 10px #00E5FF;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="custom-select" @click="toggleDropdown">
|
||||
<div class="select-display">
|
||||
<span class="select-text">{{ getSelectedLabel() }}</span>
|
||||
<span class="select-arrow" :class="{ 'arrow-up': isDropdownOpen }">▼</span>
|
||||
</div>
|
||||
<div class="select-dropdown" v-show="isDropdownOpen">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
class="select-option"
|
||||
:class="{ 'selected': item.value === selectedValue }"
|
||||
@click.stop="selectOption(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps<{
|
||||
options: Array<{ label: string; value: any }>
|
||||
selectedValue: any
|
||||
}>()
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits<{
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const isDropdownOpen = ref(false)
|
||||
|
||||
// 切换下拉菜单显示状态
|
||||
const toggleDropdown = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
isDropdownOpen.value = !isDropdownOpen.value
|
||||
}
|
||||
|
||||
// 选择选项
|
||||
const selectOption = (item: any) => {
|
||||
emit('change', item.value)
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
|
||||
// 获取选中项的标签
|
||||
const getSelectedLabel = () => {
|
||||
const selectedItem = props.options.find(
|
||||
(item: any) => item.value === props.selectedValue
|
||||
)
|
||||
return selectedItem ? selectedItem.label : '请选择'
|
||||
}
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleClickOutside = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (!target.closest('.custom-select')) {
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时添加全局点击事件监听
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-select {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 14px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.select-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
background-color: rgb(48, 110, 100);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.select-text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
margin-left: 8px;
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgb(48, 110, 100);
|
||||
border-radius: 6px;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
padding: 10px 12px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 174 KiB |
@ -0,0 +1,140 @@
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { LineGraph01 } from './index'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import { chartInitConfig } from '@/settings/designSetting'
|
||||
import dataJson from './data.json'
|
||||
export interface DataSourceItem {
|
||||
dataname: string;
|
||||
values: number[];
|
||||
datavalues: (string | number)[][];
|
||||
}
|
||||
export interface DatasType {
|
||||
title: string;
|
||||
names: string[];
|
||||
tip: string;
|
||||
dataIndex: number;
|
||||
dataSource: DataSourceItem[];
|
||||
}
|
||||
|
||||
|
||||
export const includes = ['legend', 'xAxis', 'yAxis', 'grid']
|
||||
export const seriesItem = {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
|
||||
label: {
|
||||
show: false,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
itemStyle: {
|
||||
color: null,
|
||||
borderRadius: 0
|
||||
},
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 2,
|
||||
color: '#50BB9A'
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0,
|
||||
color: 'rgba(80,187,154,0.6)'
|
||||
}, {
|
||||
offset: 1,
|
||||
color: 'rgba(80,187,154,0.3)'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
export const option = {
|
||||
dateTime: {
|
||||
selectValue: 'day',
|
||||
dataset: [
|
||||
{
|
||||
label: '当天',
|
||||
value: 'day'
|
||||
},
|
||||
{
|
||||
label: '本周',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '当月',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '本季度',
|
||||
value: 'quarter'
|
||||
},
|
||||
{
|
||||
label: '当年',
|
||||
value: 'year'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
dataset: {
|
||||
source: dataJson.dataSource[0].datavalues
|
||||
},
|
||||
grid: {
|
||||
left: '15%',
|
||||
right: '12%',
|
||||
top: 9,
|
||||
bottom: '25%'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#B7BFC6'
|
||||
}
|
||||
},
|
||||
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}s',
|
||||
color: "#4C535B"
|
||||
},
|
||||
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
series: [seriesItem]
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key: string = LineGraph01.key
|
||||
public chartConfig = cloneDeep(LineGraph01)
|
||||
public mockData = dataJson
|
||||
// 图表配置项
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
public attr = { ...chartInitConfig, x: 0, y: 0, w: 450, h: 300, zIndex: 1 }
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<!-- Echarts 全局设置 -->
|
||||
<global-setting :optionData="optionData"></global-setting>
|
||||
<CollapseItem v-for="(item, index) in seriesList" :key="index" :name="`折线图-${index + 1}`" :expanded="true">
|
||||
<SettingItemBox name="线条">
|
||||
<SettingItem name="宽度">
|
||||
<n-input-number v-model:value="item.lineStyle.width" :min="1" :max="100" size="small"
|
||||
placeholder="自动计算"></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="类型">
|
||||
<n-select v-model:value="item.lineStyle.type" size="small" :options="lineConf.lineStyle.type"></n-select>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<SettingItemBox name="实心点">
|
||||
<SettingItem name="大小">
|
||||
<n-input-number v-model:value="item.symbolSize" :min="1" :max="100" size="small"
|
||||
placeholder="自动计算"></n-input-number>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
<setting-item-box name="标签">
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="item.label.show" size="small" />
|
||||
<n-text>展示标签</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="大小">
|
||||
<n-input-number v-model:value="item.label.fontSize" size="small" :min="1"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="item.label.color"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="位置">
|
||||
<n-select v-model:value="item.label.position" :options="[
|
||||
{ label: 'top', value: 'top' },
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'right', value: 'right' },
|
||||
{ label: 'bottom', value: 'bottom' }
|
||||
]" />
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed } from 'vue'
|
||||
import { lineConf } from '@/packages/chartConfiguration/echarts/index'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes/index'
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<GlobalThemeJsonType>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const seriesList = computed(() => {
|
||||
return props.optionData.series
|
||||
})
|
||||
</script>
|
@ -0,0 +1,96 @@
|
||||
{
|
||||
"title": "报警处置情况",
|
||||
"names": [
|
||||
"报警总数",
|
||||
"未处置报警数",
|
||||
"平均处置时长"
|
||||
],
|
||||
"tip": "报警处置效率",
|
||||
"dataIndex": 0,
|
||||
"dataSource": [
|
||||
{
|
||||
"dataname": "当日",
|
||||
"values": [
|
||||
2623,
|
||||
2623,
|
||||
2623
|
||||
],
|
||||
"datavalues": [
|
||||
[
|
||||
"时间",
|
||||
"数值"
|
||||
],
|
||||
[
|
||||
"13:00",
|
||||
99
|
||||
],
|
||||
[
|
||||
"14:00",
|
||||
19
|
||||
],
|
||||
[
|
||||
"15:00",
|
||||
75
|
||||
],
|
||||
[
|
||||
"16:00",
|
||||
26
|
||||
],
|
||||
[
|
||||
"17:00",
|
||||
35
|
||||
],
|
||||
[
|
||||
"18:00",
|
||||
20
|
||||
],
|
||||
[
|
||||
"19:00",
|
||||
10
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"dataname": "昨日",
|
||||
"values": [
|
||||
2734,
|
||||
2829,
|
||||
2232
|
||||
],
|
||||
"datavalues": [
|
||||
[
|
||||
"时间",
|
||||
"数值"
|
||||
],
|
||||
[
|
||||
"13:00",
|
||||
12
|
||||
],
|
||||
[
|
||||
"14:00",
|
||||
22
|
||||
],
|
||||
[
|
||||
"15:00",
|
||||
23
|
||||
],
|
||||
[
|
||||
"16:00",
|
||||
3
|
||||
],
|
||||
[
|
||||
"17:00",
|
||||
68
|
||||
],
|
||||
[
|
||||
"18:00",
|
||||
56
|
||||
],
|
||||
[
|
||||
"19:00",
|
||||
45
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 461 B |
@ -0,0 +1,17 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
|
||||
// 曲线图
|
||||
export const LineGraph01: ConfigType = {
|
||||
key: 'LineGraph01Haz',
|
||||
chartKey: 'VLineGraph01Haz',
|
||||
conKey: 'VCLineGraph01Haz',
|
||||
title: '曲线图(带边框,标题)',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: 'Charts',
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: "LineGraph01.png",
|
||||
}
|
||||
|
||||
// 默认导出配置对象
|
||||
export default LineGraph01
|
@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<div class="go-border-box">
|
||||
|
||||
<img src="./assets/title.svg" class="svg" />
|
||||
<div class="buttonContent">
|
||||
<span class="title">{{ configData.title }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 使用新的下拉选择器组件 -->
|
||||
<CustomSelect
|
||||
:options="option.dateTime.dataset"
|
||||
:selectedValue="option.dateTime.selectValue"
|
||||
@change="handleSelectChange"
|
||||
/>
|
||||
|
||||
<div class="textContent">
|
||||
<div class="textInContent">
|
||||
<span class="smallText">{{ configData.names[0] }} </span>
|
||||
<span class="bigText">{{ configData.dataSource[selectedIndex].values[0]?.toLocaleString('en-US')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="textInContent">
|
||||
<span class="smallText">{{ configData.names[1] }}</span>
|
||||
<span class="bigText">{{ configData.dataSource[selectedIndex].values[1]?.toLocaleString('en-US')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="textInContent">
|
||||
<span class="smallText">{{ configData.names[2] }}</span>
|
||||
<span class="bigText">{{ configData.dataSource[selectedIndex].values[2]?.toLocaleString('en-US')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chartsContent">
|
||||
<span class="smallText Tips">
|
||||
<img class="icon" src="./icon01.png" alt="">
|
||||
{{ configData.tip }}</span>
|
||||
<v-chart ref="vChartRef" :init-options="initOptions" :theme="themeColor" :option="chartOption" :update-options="{
|
||||
replaceMerge: replaceMergeArr
|
||||
}" autoresize>
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed, watch, ref, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook'
|
||||
import { use } from 'echarts/core'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { LineChart } from 'echarts/charts'
|
||||
import Config, { DatasType, includes, seriesItem } from './config'
|
||||
import { mergeTheme } from '@/packages/public/chart'
|
||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||
import { useChartDataFetch } from '@/hooks'
|
||||
import { DatasetComponent, GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import isObject from 'lodash/isObject'
|
||||
import CustomSelect from './select.vue'
|
||||
|
||||
|
||||
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)
|
||||
|
||||
const option = computed(() => {
|
||||
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
})
|
||||
|
||||
use([DatasetComponent, CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent])
|
||||
|
||||
const replaceMergeArr = ref<string[]>()
|
||||
|
||||
// 生成选项数据
|
||||
const selectOptions = computed(() => {
|
||||
return configData.value.dataSource.map((item, index) => ({
|
||||
label: item.dataname,
|
||||
value: index
|
||||
}))
|
||||
})
|
||||
|
||||
const selectedIndex = ref(0); // 当前选中的数据源索引
|
||||
|
||||
|
||||
// 辅助函数:解析 mockData
|
||||
const parseMockData = (mockData: any): DatasType => {
|
||||
const DEFAULT_MOCK_DATA: DatasType = {
|
||||
title: '默认标题',
|
||||
names: ['项1', '项2', '项3'],
|
||||
tip: "默认提示",
|
||||
dataIndex: 0,
|
||||
dataSource: [{
|
||||
dataname: '默认数据',
|
||||
values: [0, 0, 0],
|
||||
datavalues: [
|
||||
['维度', '数值'],
|
||||
['0', 0],
|
||||
['1', 0],
|
||||
['2', 0]
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
let parsedData: DatasType;
|
||||
if (typeof mockData === 'string') {
|
||||
try {
|
||||
const tempParsed = JSON.parse(mockData);
|
||||
parsedData = (typeof tempParsed === 'object' && tempParsed !== null) ? tempParsed : DEFAULT_MOCK_DATA;
|
||||
} catch (e) {
|
||||
console.error('解析 mockData 字符串失败:', e);
|
||||
parsedData = DEFAULT_MOCK_DATA;
|
||||
}
|
||||
} else if (typeof mockData === 'object' && mockData !== null) {
|
||||
parsedData = mockData;
|
||||
} else {
|
||||
parsedData = DEFAULT_MOCK_DATA;
|
||||
}
|
||||
|
||||
if (parsedData.dataIndex === undefined || parsedData.dataIndex < 0 || parsedData.dataIndex >= parsedData.dataSource.length) {
|
||||
parsedData.dataIndex = 0;
|
||||
}
|
||||
|
||||
if (!parsedData.dataSource || parsedData.dataSource.length === 0) {
|
||||
parsedData.dataSource = DEFAULT_MOCK_DATA.dataSource;
|
||||
}
|
||||
|
||||
if (!parsedData.names || parsedData.names.length === 0) parsedData.names = DEFAULT_MOCK_DATA.names;
|
||||
|
||||
|
||||
return parsedData;
|
||||
};
|
||||
|
||||
|
||||
const configData = computed<DatasType>(() => {
|
||||
return parseMockData(props.chartConfig.mockData);
|
||||
});
|
||||
|
||||
// 监听 mockData 变化,并初始化 selectedIndex
|
||||
watch(
|
||||
() => props.chartConfig.mockData,
|
||||
(newMockData) => {
|
||||
const parsedData = parseMockData(newMockData);
|
||||
selectedIndex.value = parsedData.dataIndex;
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
|
||||
|
||||
const chartOption = computed(() => {
|
||||
const mergedOption = mergeTheme(props.chartConfig.option, props.themeSetting, includes);
|
||||
|
||||
|
||||
const currentDataSourceItem = configData.value.dataSource[selectedIndex.value];
|
||||
const currentDatavalues = currentDataSourceItem ? currentDataSourceItem.datavalues : [];
|
||||
|
||||
|
||||
if (!mergedOption.dataset) {
|
||||
mergedOption.dataset = {};
|
||||
}
|
||||
mergedOption.dataset.source = currentDatavalues;
|
||||
|
||||
return mergedOption;
|
||||
});
|
||||
|
||||
const wrapperStyle = computed(() => ({
|
||||
width: props.chartConfig.attr.w + 'px',
|
||||
height: "16%"
|
||||
}))
|
||||
|
||||
|
||||
// dataset 无法变更条数的补丁 (保持不变)
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
(newData: { dimensions: any }, oldData) => {
|
||||
try {
|
||||
if (!isObject(newData) || !('dimensions' in newData)) return
|
||||
if (Array.isArray(newData?.dimensions)) {
|
||||
const seriesArr = []
|
||||
for (let i = 0; i < newData.dimensions.length - 1; i++) {
|
||||
seriesArr.push(seriesItem)
|
||||
}
|
||||
replaceMergeArr.value = ['series']
|
||||
props.chartConfig.option.series = seriesArr
|
||||
nextTick(() => {
|
||||
replaceMergeArr.value = []
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
// 处理下拉选择器变化
|
||||
const handleSelectChange = (value: any) => {
|
||||
props.chartConfig.option.dateTime.selectValue = value
|
||||
// selectedIndex.value = value
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@include go(border-box) {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
// 不渐变
|
||||
// background-color: #0E121B;
|
||||
// 渐变
|
||||
background: linear-gradient(to top,
|
||||
rgba(14, 18, 27, 1) 0%,
|
||||
rgba(14, 18, 27, 0.6) 100%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 10px;
|
||||
padding: 2px;
|
||||
/* 边框宽度 */
|
||||
background: linear-gradient(to top,
|
||||
rgba(128, 128, 128, 0.3),
|
||||
rgba(128, 128, 128, 0));
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.textContent {
|
||||
padding-top: 20px;
|
||||
/* background-color: brown; */
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.textInContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chartsContent {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
/* background-color: blue; */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.textInContent .smallText {
|
||||
font-size: 11px;
|
||||
color: #a9a9a9;
|
||||
}
|
||||
|
||||
.textInContent .bigText {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
color: transparent;
|
||||
margin-top: 0.3em;
|
||||
|
||||
}
|
||||
|
||||
.textContent :nth-child(1) .bigText {
|
||||
background: linear-gradient(to right, #ffdc92, #F7BDA7);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.textContent :nth-child(2) .bigText {
|
||||
background: linear-gradient(to right, #86D8F3, #6CDEDF);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.textContent :nth-child(3) .bigText {
|
||||
background: linear-gradient(to right, #6CDFBE, #A2E9D9);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.Tips {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
margin-top: 9px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 10px;
|
||||
height: 1.6em;
|
||||
/* width: 8em; */
|
||||
width: fit-content;
|
||||
padding: 0.23em 0.4em;
|
||||
color: rgb(210, 210, 210);
|
||||
background-color: #273F40;
|
||||
border-radius: 7px;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.border {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
height: 15%;
|
||||
// background-color: #F7BDA7;
|
||||
}
|
||||
|
||||
.buttonContent {
|
||||
position: absolute;
|
||||
margin-top: 10px;
|
||||
padding-left: 5.2em;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.buttonContent span {
|
||||
|
||||
font-size: 15px;
|
||||
color: #ffffff;
|
||||
letter-spacing: 2px;
|
||||
font-weight: 500;
|
||||
text-shadow: -4px -4px 10px #3B8ED4;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
}
|
||||
</style>
|
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="custom-select" @click="toggleDropdown">
|
||||
<div class="select-display">
|
||||
<span class="select-text">{{ getSelectedLabel() }}</span>
|
||||
<span class="select-arrow" :class="{ 'arrow-up': isDropdownOpen }">▼</span>
|
||||
</div>
|
||||
<div class="select-dropdown" v-show="isDropdownOpen">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
class="select-option"
|
||||
:class="{ 'selected': item.value === selectedValue }"
|
||||
@click.stop="selectOption(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps<{
|
||||
options: Array<{ label: string; value: any }>
|
||||
selectedValue: any
|
||||
}>()
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits<{
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const isDropdownOpen = ref(false)
|
||||
|
||||
// 切换下拉菜单显示状态
|
||||
const toggleDropdown = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
isDropdownOpen.value = !isDropdownOpen.value
|
||||
}
|
||||
|
||||
// 选择选项
|
||||
const selectOption = (item: any) => {
|
||||
emit('change', item.value)
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
|
||||
// 获取选中项的标签
|
||||
const getSelectedLabel = () => {
|
||||
const selectedItem = props.options.find(
|
||||
(item: any) => item.value === props.selectedValue
|
||||
)
|
||||
return selectedItem ? selectedItem.label : '请选择'
|
||||
}
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleClickOutside = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (!target.closest('.custom-select')) {
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时添加全局点击事件监听
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-select {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 14px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.select-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
background-color: #086C67;
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.select-text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
margin-left: 8px;
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #086C67;
|
||||
border-radius: 6px;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
padding: 10px 12px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,58 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { MaxTrimConfig } from './index'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import { chartInitConfig } from '@/settings/designSetting'
|
||||
|
||||
export enum WritingModeEnum {
|
||||
HORIZONTAL = '水平',
|
||||
VERTICAL = '垂直'
|
||||
}
|
||||
|
||||
export const WritingModeObject = {
|
||||
[WritingModeEnum.HORIZONTAL]: 'horizontal-tb',
|
||||
[WritingModeEnum.VERTICAL]: 'vertical-rl'
|
||||
}
|
||||
|
||||
export enum FontWeightEnum {
|
||||
NORMAL = '常规',
|
||||
BOLD = '加粗',
|
||||
}
|
||||
|
||||
export enum FontStyleEnum {
|
||||
NORMAL = '常规',
|
||||
ITALIC = '斜体',
|
||||
}
|
||||
|
||||
export const FontWeightObject = {
|
||||
[FontWeightEnum.NORMAL]: 'normal',
|
||||
[FontWeightEnum.BOLD]: 'bold',
|
||||
}
|
||||
|
||||
export const FontStyleObject = {
|
||||
[FontStyleEnum.NORMAL]: 'normal',
|
||||
[FontStyleEnum.ITALIC]: 'italic',
|
||||
}
|
||||
|
||||
export const option = {
|
||||
dataset: '危化品库场景数据看板',
|
||||
fontSize: 38,
|
||||
fontColor: '#ffffff',
|
||||
paddingX: 35,
|
||||
paddingY: 6,
|
||||
textAlign: 'start', // 水平对齐方式
|
||||
fontWeight: 'bold',
|
||||
fontStyle: 'italic',
|
||||
// 字间距
|
||||
letterSpacing: 10,
|
||||
writingMode: 'horizontal-tb',
|
||||
backgroundColor: '#00000000'
|
||||
}
|
||||
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = MaxTrimConfig.key
|
||||
public chartConfig = cloneDeep(MaxTrimConfig)
|
||||
public attr = { ...chartInitConfig, x: 0, y: 0, w: 1920, h: 94, zIndex: 1 }
|
||||
public option = cloneDeep(option)
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<collapse-item name="信息" :expanded="true">
|
||||
<setting-item-box name="标题" :alone="true">
|
||||
<setting-item>
|
||||
<n-input v-model:value="optionData.dataset" type="textarea" size="small"></n-input>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
|
||||
<collapse-item name="样式" :expanded="true">
|
||||
<setting-item-box name="标题">
|
||||
<setting-item name="颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.fontColor"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="阴影颜色">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="optionData.fontShadowColor"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="字体大小">
|
||||
<n-input-number v-model:value="optionData.fontSize" size="small" placeholder="字体大小"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="字体粗细">
|
||||
<n-select v-model:value="optionData.fontWeight" size="small" :options="fontWeightOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="字体风格">
|
||||
<n-select v-model:value="optionData.fontStyle" size="small" :options="fontStyleOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="X轴内边距">
|
||||
<n-input-number v-model:value="optionData.paddingX" size="small" placeholder="输入内边距"></n-input-number>
|
||||
</setting-item>
|
||||
<setting-item name="Y轴内边距">
|
||||
<n-input-number v-model:value="optionData.paddingY" size="small" placeholder="输入内边距"></n-input-number>
|
||||
</setting-item>
|
||||
|
||||
<setting-item name="水平对齐">
|
||||
<n-select v-model:value="optionData.textAlign" size="small" :options="textAlignOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="文本方向">
|
||||
<n-select v-model:value="optionData.writingMode" size="small" :options="verticalOptions" />
|
||||
</setting-item>
|
||||
|
||||
<setting-item name="字间距">
|
||||
<n-input-number v-model:value="optionData.letterSpacing" size="small" placeholder="输入字间距"></n-input-number>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { option, WritingModeEnum, WritingModeObject, FontWeightEnum, FontWeightObject, FontStyleEnum, FontStyleObject } from './config'
|
||||
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const textAlignOptions = [
|
||||
{ label: '左对齐', value: 'start' },
|
||||
{ label: '居中', value: 'center' },
|
||||
{ label: '右对齐', value: 'end' }
|
||||
]
|
||||
|
||||
const verticalOptions = [
|
||||
{
|
||||
label: WritingModeEnum.HORIZONTAL,
|
||||
value: WritingModeObject[WritingModeEnum.HORIZONTAL]
|
||||
},
|
||||
{
|
||||
label: WritingModeEnum.VERTICAL,
|
||||
value: WritingModeObject[WritingModeEnum.VERTICAL]
|
||||
}
|
||||
]
|
||||
const fontWeightOptions = [
|
||||
{
|
||||
label: FontWeightEnum.NORMAL,
|
||||
value: FontWeightObject[FontWeightEnum.NORMAL]
|
||||
},
|
||||
{
|
||||
label: FontWeightEnum.BOLD,
|
||||
value: FontWeightObject[FontWeightEnum.BOLD]
|
||||
}
|
||||
]
|
||||
const fontStyleOptions = [
|
||||
{
|
||||
label: FontStyleEnum.NORMAL,
|
||||
value: FontStyleObject[FontStyleEnum.NORMAL]
|
||||
},
|
||||
{
|
||||
label: FontStyleEnum.ITALIC,
|
||||
value: FontStyleObject[FontStyleEnum.ITALIC]
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, ChartFrameEnum } from '@/packages/index.d'
|
||||
|
||||
export const MaxTrimConfig: ConfigType = {
|
||||
key: 'MaxTrimHaz',
|
||||
chartKey: 'VMaxTrimHaz',
|
||||
conKey: 'VCMaxTrimHaz',
|
||||
title: '最大装饰条',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: 'Charts',
|
||||
chartFrame: ChartFrameEnum.COMMON,
|
||||
image: 'MaxTrim.png'
|
||||
}
|
||||
export default MaxTrimConfig
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 174 KiB |
@ -0,0 +1,237 @@
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { PieCenterConfig } from './index'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend']
|
||||
|
||||
// 其它配置
|
||||
const otherConfig = {
|
||||
dateTime: {
|
||||
selectValue: 'day',
|
||||
dataset: [
|
||||
{
|
||||
label: '当天',
|
||||
value: 'day'
|
||||
},
|
||||
{
|
||||
label: '本周',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '当月',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '本季度',
|
||||
value: 'quarter'
|
||||
},
|
||||
{
|
||||
label: '当年',
|
||||
value: 'year'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const option = {
|
||||
...otherConfig,
|
||||
backgroundColor: 'transparent',
|
||||
legend: {
|
||||
show: true,
|
||||
orient: 'vertical',
|
||||
left: '60%', // 默认更靠左一点,避免截断
|
||||
top: 'center',
|
||||
icon: 'rect',
|
||||
showEmpty:false,
|
||||
// itemWidth: 10,
|
||||
itemHeight: 8,
|
||||
itemGap: 5,
|
||||
selectedMode: 'multiple',
|
||||
// padding: [15, 20, 15, 20],
|
||||
textStyle: {
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
fontSize: 16
|
||||
},
|
||||
formatter: function (name: string) {
|
||||
return name
|
||||
}
|
||||
},
|
||||
// color: ['rgba(255, 215, 0, 1)', 'rgba(74, 144, 226, 1)', 'rgba(80, 227, 194, 1)', 'rgba(126, 211, 33, 1)', 'rgba(144, 19, 254, 1)'],
|
||||
dataset: { ...dataJson },
|
||||
series: [
|
||||
// 最外浅灰细边框
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['84%', '86%'],
|
||||
center: ['30%', '48%'],
|
||||
silent: true,
|
||||
z: 1,
|
||||
data: [{ value: 1, name: '', itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(200, 200, 200, 0.01)' },
|
||||
{ offset: 0.5, color: 'rgba(200, 200, 200, 0.1)' },
|
||||
{ offset: 1, color: 'rgba(200, 200, 200, 0.01)' },
|
||||
]
|
||||
}
|
||||
} }],
|
||||
label: { show: false },
|
||||
labelLine: { show: false }
|
||||
},
|
||||
// 中层蓝色细边框
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['73%', '75%'],
|
||||
center: ['30%', '48%'],
|
||||
silent: true,
|
||||
z: 2,
|
||||
data: [{
|
||||
value: 1, name: '', itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0.2)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 1)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
}],
|
||||
label: { show: false },
|
||||
labelLine: { show: false }
|
||||
},
|
||||
// 内层(蓝->黑)渐变环:在外边框里面
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['66%', '73%'],
|
||||
center: ['30%', '48%'],
|
||||
silent: true,
|
||||
z: 3,
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: '',
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(10, 21, 35, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(127, 216, 255, 0.3)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
label: { show: false },
|
||||
labelLine: { show: false }
|
||||
},
|
||||
// 主数据环:内半径加大,让"最内环更薄"
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['50%', '66%'], // 原 ['35%','65%'] -> 更薄的环
|
||||
center: ['30%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
padAngle: 4, // 扇区间隙
|
||||
z: 9,
|
||||
itemStyle: {
|
||||
borderColor: 'rgba(13, 16, 22, 1)', // 用深色细边强调间隙
|
||||
borderWidth: 2,
|
||||
borderRadius: 0
|
||||
},
|
||||
label: { show: false },
|
||||
labelLine: { show: false },
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: { shadowBlur: 12, shadowColor: 'rgba(0, 0, 0, 0.35)' }
|
||||
},
|
||||
data: dataJson.source
|
||||
},
|
||||
// 内部渐变装饰环(在空洞边缘,提升质感)
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['48%', '50%'], // 位于主环内侧
|
||||
center: ['30%', '50%'],
|
||||
silent: true,
|
||||
z: 0,
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: '',
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(90, 220, 255, 0.15)' },
|
||||
{ offset: 1, color: 'rgba(90, 220, 255, 0.15)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
label: { show: false },
|
||||
labelLine: { show: false }
|
||||
},
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['0%', '48%'], // 位于主环内侧
|
||||
center: ['30%', '50%'],
|
||||
// silent: true,
|
||||
z: 10,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'center',
|
||||
formatter: '{a|23211}\n\n{b|总数}',
|
||||
rich: {
|
||||
a: {
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
fontSize: 34,
|
||||
align: 'center'
|
||||
},
|
||||
b: {
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
fontSize: 16,
|
||||
align: 'center'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data: [
|
||||
{
|
||||
value: 1,
|
||||
name: '',
|
||||
itemStyle: {
|
||||
color: 'transparent'
|
||||
}
|
||||
}
|
||||
],
|
||||
labelLine: { show: false }
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key: string = PieCenterConfig.key
|
||||
public chartConfig = cloneDeep(PieCenterConfig)
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 基础配置可以复用PieCommon的配置面板 -->
|
||||
<CollapseItem name="基础配置" :expanded="true">
|
||||
<SettingItemBox name="图例">
|
||||
<SettingItem name="显示">
|
||||
<n-switch v-model:value="optionData.legend.show" size="small" />
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
</CollapseItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
import { NSwitch } from 'naive-ui'
|
||||
import { option } from './config'
|
||||
|
||||
defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
"dimensions": ["name", "value", "itemColor", "borderColor"],
|
||||
"source": [
|
||||
{
|
||||
"name": "类型1",
|
||||
"value": 5830
|
||||
},
|
||||
{
|
||||
"name": "类型2",
|
||||
"value": 7020
|
||||
},
|
||||
{
|
||||
"name": "类型3",
|
||||
"value": 4220
|
||||
},
|
||||
{
|
||||
"name": "类型4",
|
||||
"value": 5180
|
||||
},
|
||||
{
|
||||
"name": "类型5",
|
||||
"value": 2340
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../../../index.d'
|
||||
|
||||
export const PieCenterConfig: ConfigType = {
|
||||
key: 'PieCenterHaz',
|
||||
chartKey: 'VPieCenterHaz',
|
||||
conKey: 'VCPieCenterHaz',
|
||||
title: '报警统计',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: 'pie_center.png'
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="go-border-box">
|
||||
<img src="./assets/title.svg" class="svg" />
|
||||
<div class="header-title">报警统计</div>
|
||||
<v-chart ref="vChartRef" autoresize :init-options="initOptions" :theme="themeColor" :option="option"></v-chart>
|
||||
<!-- 使用新的下拉选择器组件 -->
|
||||
<CustomSelect
|
||||
:options="option.dateTime.dataset"
|
||||
:selectedValue="option.dateTime.selectValue"
|
||||
@change="handleSelectChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType, onMounted, watch, ref, onUnmounted } 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, GraphicComponent } from 'echarts/components'
|
||||
import CustomSelect from './select.vue'
|
||||
import dataJson from './data.json'
|
||||
|
||||
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, GraphicComponent])
|
||||
|
||||
const option = computed(() => {
|
||||
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
})
|
||||
|
||||
function calculateTotal(data: any) {
|
||||
if (!data || !data.source) return 0
|
||||
return data.source.reduce((total: number, item: any) => {
|
||||
return total + (item[data.dimensions[1]] || 0)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function calculatePercentage(value: number, total: number) {
|
||||
return total > 0 ? ((value / total) * 100).toFixed(1) : '0.0'
|
||||
}
|
||||
|
||||
const updateChartData = (newData: any) => {
|
||||
if (!newData) return
|
||||
|
||||
const total = calculateTotal(newData)
|
||||
if (props.chartConfig.option.graphic?.[0]) {
|
||||
props.chartConfig.option.graphic[0].style.text = total.toString()
|
||||
}
|
||||
|
||||
// 统一显示为 “名称 百分比”
|
||||
props.chartConfig.option.legend.formatter = (name: string) => {
|
||||
const item = newData.source.find((it: any) => it[newData.dimensions[0]] === name)
|
||||
if (!item) return null
|
||||
const val = item[newData.dimensions[1]] ?? 0
|
||||
let p = calculatePercentage(val, total)
|
||||
// return `{row|${name} ${p}%}`
|
||||
if(Number(p)<10){
|
||||
p=' '+p
|
||||
}
|
||||
return `{name|${name}}{value|${p}}{unit|%}`;
|
||||
}
|
||||
|
||||
// 每行独立渐变背景
|
||||
props.chartConfig.option.legend.textStyle = {
|
||||
color: '#fff',
|
||||
rich: {
|
||||
name: {
|
||||
width: 80,
|
||||
padding: [8, 0, 8, 10],
|
||||
color: '#999',
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0)' },
|
||||
{ offset: 0.5, color: 'rgba(58, 160, 255, 0.05)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 0.15)' }
|
||||
]
|
||||
},
|
||||
},
|
||||
value: {
|
||||
width: 30,
|
||||
padding: [8, 0, 8, 0],
|
||||
textAlign: 'right',
|
||||
fontFamily: 'monospace',
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0)' },
|
||||
{ offset: 0.5, color: 'rgba(58, 160, 255, 0.05)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 0.15)' }
|
||||
]
|
||||
},
|
||||
},
|
||||
unit: {
|
||||
width: 10,
|
||||
padding: [8, 10, 8, 0],
|
||||
textAlign: 'left',
|
||||
color: '#999',
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(58, 160, 255, 0)' },
|
||||
{ offset: 0.5, color: 'rgba(58, 160, 255, 0.05)' },
|
||||
{ offset: 1, color: 'rgba(58, 160, 255, 0.15)' }
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const totalValue=newData.source.reduce((total: number, item: any) => {
|
||||
return total + (item.value || 0)
|
||||
}, 0)
|
||||
props.chartConfig.option.series[props.chartConfig.option.series.length-1].label.formatter=`{a|${totalValue}}\n\n{b|总数}`
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
if (newData) {
|
||||
props.chartConfig.option.dataset = newData
|
||||
updateChartData(newData)
|
||||
}
|
||||
},
|
||||
{ deep: false, immediate: true }
|
||||
)
|
||||
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
props.chartConfig.option.dataset = newData
|
||||
updateChartData(newData)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateChartData(props.chartConfig.option.dataset)
|
||||
})
|
||||
|
||||
// 处理下拉选择器变化
|
||||
const handleSelectChange = (value: any) => {
|
||||
props.chartConfig.option.dateTime.selectValue = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@include go(border-box) {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
// 不渐变
|
||||
// background-color: #0E121B;
|
||||
// 渐变
|
||||
background: linear-gradient(to top,
|
||||
rgba(14, 18, 27, 1) 0%,
|
||||
rgba(14, 18, 27, 0.6) 100%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
/* 边框宽度 */
|
||||
background: linear-gradient(to top,
|
||||
rgba(128, 128, 128, 0.3),
|
||||
rgba(128, 128, 128, 0));
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
left: 80px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #eee;
|
||||
font-style: italic;
|
||||
text-shadow: 0 0 10px #00E5FF;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="custom-select" @click="toggleDropdown">
|
||||
<div class="select-display">
|
||||
<span class="select-text">{{ getSelectedLabel() }}</span>
|
||||
<span class="select-arrow" :class="{ 'arrow-up': isDropdownOpen }">▼</span>
|
||||
</div>
|
||||
<div class="select-dropdown" v-show="isDropdownOpen">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
class="select-option"
|
||||
:class="{ 'selected': item.value === selectedValue }"
|
||||
@click.stop="selectOption(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps<{
|
||||
options: Array<{ label: string; value: any }>
|
||||
selectedValue: any
|
||||
}>()
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits<{
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const isDropdownOpen = ref(false)
|
||||
|
||||
// 切换下拉菜单显示状态
|
||||
const toggleDropdown = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
isDropdownOpen.value = !isDropdownOpen.value
|
||||
}
|
||||
|
||||
// 选择选项
|
||||
const selectOption = (item: any) => {
|
||||
emit('change', item.value)
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
|
||||
// 获取选中项的标签
|
||||
const getSelectedLabel = () => {
|
||||
const selectedItem = props.options.find(
|
||||
(item: any) => item.value === props.selectedValue
|
||||
)
|
||||
return selectedItem ? selectedItem.label : '请选择'
|
||||
}
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleClickOutside = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (!target.closest('.custom-select')) {
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时添加全局点击事件监听
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-select {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 14px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.select-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
background-color: rgb(48, 110, 100);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.select-text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
margin-left: 8px;
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgb(48, 110, 100);
|
||||
border-radius: 6px;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
padding: 10px 12px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 174 KiB |
@ -0,0 +1,17 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { SmallBorderConfig } from './index'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
export const option = {
|
||||
colors: ['#1089ff', '#0000ff'],
|
||||
backgroundColor: '#00000000'
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = SmallBorderConfig.key
|
||||
public chartConfig = cloneDeep(SmallBorderConfig)
|
||||
public option = cloneDeep(option)
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<CollapseItem name="边框" :expanded="true">
|
||||
<SettingItemBox
|
||||
:name="`颜色-${index + 1}`"
|
||||
v-for="(item, index) in optionData.colors"
|
||||
:key="index"
|
||||
>
|
||||
<SettingItem name="颜色">
|
||||
<n-color-picker
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
v-model:value="optionData.colors[index]"
|
||||
></n-color-picker>
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="optionData.colors[index] = option.colors[index]"
|
||||
>
|
||||
恢复默认
|
||||
</n-button>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
</CollapseItem>
|
||||
|
||||
<CollapseItem name="背景" :expanded="true">
|
||||
<SettingItemBox name="颜色">
|
||||
<SettingItem>
|
||||
<n-color-picker
|
||||
size="small"
|
||||
:modes="['hex']"
|
||||
v-model:value="optionData.backgroundColor"
|
||||
></n-color-picker>
|
||||
</SettingItem>
|
||||
</SettingItemBox>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import {
|
||||
CollapseItem,
|
||||
SettingItemBox,
|
||||
SettingItem
|
||||
} from '@/components/Pages/ChartItemSetting'
|
||||
import { option } from './config'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,17 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d'
|
||||
// 曲线图
|
||||
export const SmallBorderConfig: ConfigType = {
|
||||
key: 'SmallBorder',
|
||||
chartKey: 'VSmallBorder',
|
||||
conKey: 'VCSmallBorder',
|
||||
title: '带标题小边框',
|
||||
category: 'MyComponents',
|
||||
categoryName: '自定义组件',
|
||||
package: 'Charts',
|
||||
chartFrame: ChartFrameEnum.COMMON,
|
||||
image: "SmallBorder.png",
|
||||
}
|
||||
|
||||
// 默认导出配置对象
|
||||
export default SmallBorderConfig
|
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="go-small-border">
|
||||
<div class="border-content">
|
||||
<div class="svg-content">
|
||||
<img src="./assets/title.svg" class="svg" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">export default { name: "SmallBorder" }</script>
|
||||
<style lang="scss" scoped>
|
||||
$border-radius: 10px;
|
||||
$border-width: 2px;
|
||||
$bg-color-start: rgba(14, 18, 27, 1);
|
||||
$bg-color-end: rgba(14, 18, 27, 0.6);
|
||||
$border-gradient-start: rgba(128, 128, 128, 0.3);
|
||||
$border-gradient-end: rgba(128, 128, 128, 0);
|
||||
|
||||
@include go(small-border) {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& .border-content {
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border-radius: $border-radius;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(to top, $bg-color-start, $bg-color-end);
|
||||
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: $border-radius;
|
||||
padding: $border-width;
|
||||
background: linear-gradient(to top, $border-gradient-start, $border-gradient-end);
|
||||
// 实现边框遮罩效果,只显示边框区域
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none; // 确保点击能穿透到下层元素
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
& .svg-content {
|
||||
// z-index: 2;
|
||||
width: 100%;
|
||||
height: 12%;
|
||||
// background-color: antiquewhite;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,54 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { TopAlarmsConfig } from './index'
|
||||
import dataJson from './data.json'
|
||||
export const option = {
|
||||
dateTime: {
|
||||
selectValue: 'day',
|
||||
dataset: [
|
||||
{
|
||||
label: '当天',
|
||||
value: 'day'
|
||||
},
|
||||
{
|
||||
label: '本周',
|
||||
value: 'week'
|
||||
},
|
||||
{
|
||||
label: '当月',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '本季度',
|
||||
value: 'quarter'
|
||||
},
|
||||
{
|
||||
label: '当年',
|
||||
value: 'year'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
dataset: dataJson.source,
|
||||
title: '未处置报警数TOP5',
|
||||
titleColor: '#ffffff',
|
||||
titleSize: 17,
|
||||
rankColor: '#5AA1AD',
|
||||
nameColor: '#eeeeee',
|
||||
valueColor: '#eeeeee',
|
||||
barColorStart: '#2f72b5',
|
||||
barColorEnd: '#99C6E6',
|
||||
dropdownOptions: ['当日', '当月', '当年'],
|
||||
dropdownDefault: '当日',
|
||||
iconColor: '#00E5FF',
|
||||
letterSpacing: 1,
|
||||
paddingX: 40,
|
||||
paddingY: -5,
|
||||
isShowButton: false
|
||||
}
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = TopAlarmsConfig.key
|
||||
public chartConfig = TopAlarmsConfig
|
||||
public option = option
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="go-top-alarms-config">
|
||||
<n-form-item label="标题">
|
||||
<n-input v-model:value="option.title" />
|
||||
</n-form-item>
|
||||
<n-form-item label="标题大小">
|
||||
<n-input-number v-model:value="option.titleSize" />
|
||||
</n-form-item>
|
||||
<n-form-item label="下拉选项">
|
||||
<n-dynamic-input v-model:value="option.dropdownOptions" placeholder="请输入选项" />
|
||||
</n-form-item>
|
||||
<n-form-item label="默认选项">
|
||||
<n-input v-model:value="option.dropdownDefault" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { option } from './config'
|
||||
|
||||
defineProps({
|
||||
option: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"source": [
|
||||
{ "rank": "TOP1", "name": "企业名称企业名称企业名称企业名称", "value": 932 },
|
||||
{ "rank": "TOP2", "name": "企业名称企业名称企业名称企业名称", "value": 899 },
|
||||
{ "rank": "TOP3", "name": "企业名称企业名称企业名称企业名称", "value": 702 },
|
||||
{ "rank": "TOP4", "name": "企业名称企业名称企业名称企业名称", "value": 543 },
|
||||
{ "rank": "TOP5", "name": "企业名称企业名称企业名称企业名称", "value": 208 }
|
||||
]
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
|
||||
export const TopAlarmsConfig: ConfigType = {
|
||||
key: 'TopAlarmsHaz',
|
||||
chartKey: 'VTopAlarmsHaz',
|
||||
conKey: 'VCTopAlarmsHaz',
|
||||
title: 'Top报警',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.COMMON,
|
||||
image: 'top_alarms.png'
|
||||
}
|
||||
export default TopAlarmsConfig
|
@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<SmallBorder class="SmallBorder">
|
||||
<div class="go-top-alarms">
|
||||
<CustomSelect
|
||||
:options="option.dateTime.dataset"
|
||||
:selectedValue="option.dateTime.selectValue"
|
||||
@change="handleSelectChange"
|
||||
class="top-select"
|
||||
/>
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<div class="title-text">
|
||||
<span :style="{
|
||||
color: option.titleColor,
|
||||
fontSize: option.titleSize + 'px',
|
||||
marginTop: option.paddingY + 'px',
|
||||
marginLeft: option.paddingX + 'px',
|
||||
letterSpacing: option.letterSpacing + 'px'
|
||||
}">{{ option.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<n-dropdown v-if='option.isShowButton' trigger="hover" :options="dropdownOptions" @select="handleSelect">
|
||||
<div class="dropdown-button">
|
||||
{{ selectedOption }}
|
||||
<svg-icon icon-class="arrow-down" class="arrow-down-icon" />
|
||||
</div>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ul>
|
||||
<li v-for="(item, index) in option.dataset" :key="index" class="alarm-item">
|
||||
<div class="item-info">
|
||||
<span class="rank" :style="{ color: option.rankColor }">
|
||||
<span class="rank-icon"></span>
|
||||
{{ item.rank }}
|
||||
</span>
|
||||
<span class="name" :style="{ color: option.nameColor }">{{ item.name }}</span>
|
||||
|
||||
</div>
|
||||
<div class="item-value">
|
||||
<span class="value" :style="{ color: option.valueColor }">{{ item.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar">
|
||||
<div class="progress" :style="{
|
||||
width: calculateWidth(item.value),
|
||||
background: `linear-gradient(to right, ${option.barColorStart}, ${option.barColorEnd})`
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</SmallBorder>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, PropType } from 'vue'
|
||||
import { option as configOption } from './config'
|
||||
import SmallBorder from '../SmallBorder/index.vue'
|
||||
import CustomSelect from './select.vue'
|
||||
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<{ option: typeof configOption }>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const option = computed(() => props.chartConfig.option)
|
||||
|
||||
const selectedOption = ref(props.chartConfig.option.dropdownDefault)
|
||||
|
||||
const dropdownOptions = computed(() => {
|
||||
return props.chartConfig.option.dropdownOptions.map(opt => ({
|
||||
label: opt,
|
||||
key: opt
|
||||
}))
|
||||
})
|
||||
|
||||
const maxValue = computed(() => {
|
||||
const values = props.chartConfig.option.dataset.map(item => item.value)
|
||||
return Math.max(...values, 1) // Avoid division by zero
|
||||
})
|
||||
|
||||
const calculateWidth = (value: number) => {
|
||||
if (maxValue.value === 0) return '0%'
|
||||
return `${(value / maxValue.value) * 80}%` // Max width 80% to leave space for value
|
||||
}
|
||||
|
||||
const handleSelect = (key: string) => {
|
||||
selectedOption.value = key
|
||||
}
|
||||
|
||||
// 处理下拉选择器变化
|
||||
const handleSelectChange = (value: any) => {
|
||||
props.chartConfig.option.dateTime.selectValue = value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.SmallBorder {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.go-top-alarms {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -10px;
|
||||
// background-color: #0a162b;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
|
||||
.top-select {
|
||||
top:20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
||||
.title-text {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px 0 20px;
|
||||
margin-left: 0px;
|
||||
clip-path: polygon(10% 0, 100% 0, 100% 100%, 0% 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
height: 30px;
|
||||
padding: 0 15px;
|
||||
background-color: #034a4b;
|
||||
border: 1px solid #00e5ff;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
|
||||
.arrow-down-icon {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-top: -15px;
|
||||
padding-right: 10px;
|
||||
font-size: 12.5px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
}
|
||||
|
||||
.alarm-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
'info value'
|
||||
'bar bar';
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
gap: 6px 15px;
|
||||
|
||||
.item-info {
|
||||
grid-area: info;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.rank {
|
||||
font-weight: normal;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rank-icon {
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: #00e5ff;
|
||||
margin-right: 8px;
|
||||
margin-left: 6px;
|
||||
box-shadow: 0 0 6px #00e5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.item-value {
|
||||
grid-area: value;
|
||||
text-align: right;
|
||||
// font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.progress-bar-wrapper {
|
||||
grid-area: bar;
|
||||
width: calc(100% - 60px);
|
||||
position: relative;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
.progress {
|
||||
height: 100%;
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="custom-select" @click="toggleDropdown">
|
||||
<div class="select-display">
|
||||
<span class="select-text">{{ getSelectedLabel() }}</span>
|
||||
<span class="select-arrow" :class="{ 'arrow-up': isDropdownOpen }">▼</span>
|
||||
</div>
|
||||
<div class="select-dropdown" v-show="isDropdownOpen">
|
||||
<div
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
class="select-option"
|
||||
:class="{ 'selected': item.value === selectedValue }"
|
||||
@click.stop="selectOption(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps<{
|
||||
options: Array<{ label: string; value: any }>
|
||||
selectedValue: any
|
||||
}>()
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits<{
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const isDropdownOpen = ref(false)
|
||||
|
||||
// 切换下拉菜单显示状态
|
||||
const toggleDropdown = (event: Event) => {
|
||||
event.stopPropagation()
|
||||
isDropdownOpen.value = !isDropdownOpen.value
|
||||
}
|
||||
|
||||
// 选择选项
|
||||
const selectOption = (item: any) => {
|
||||
emit('change', item.value)
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
|
||||
// 获取选中项的标签
|
||||
const getSelectedLabel = () => {
|
||||
const selectedItem = props.options.find(
|
||||
(item: any) => item.value === props.selectedValue
|
||||
)
|
||||
return selectedItem ? selectedItem.label : '请选择'
|
||||
}
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
const handleClickOutside = (event: Event) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (!target.closest('.custom-select')) {
|
||||
isDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时添加全局点击事件监听
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-select {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 14px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.select-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
background-color: rgb(48, 110, 100);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.select-text {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
margin-left: 8px;
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgb(48, 110, 100);
|
||||
border-radius: 6px;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
padding: 10px 12px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,17 @@
|
||||
import { yushiVideoConfig } from './yushiVideoHaz/index'
|
||||
import { MaxTrimConfig } from './MaxTrimHaz/index'
|
||||
import { PieCenterConfig } from './PieCenterHaz/index'
|
||||
import { LineDropdownConfig } from './LineDropdownHaz/index'
|
||||
import { AlarmListConfig } from './AlarmListHaz/index'
|
||||
import { LineGraph01 } from './LineGraph01Haz/index'
|
||||
import {TopAlarmsConfig} from './TopAlarmsHaz/index'
|
||||
|
||||
export default [
|
||||
yushiVideoConfig,
|
||||
MaxTrimConfig,
|
||||
PieCenterConfig,
|
||||
LineDropdownConfig,
|
||||
AlarmListConfig,
|
||||
LineGraph01,
|
||||
TopAlarmsConfig
|
||||
]
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 174 KiB |
File diff suppressed because one or more lines are too long
@ -0,0 +1,35 @@
|
||||
import { PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { yushiVideoConfig } from './index'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
export const option = {
|
||||
// 摄像头id
|
||||
dataset: {
|
||||
defaultValue: '',
|
||||
list: []
|
||||
},
|
||||
showTree: true,
|
||||
// 循环播放
|
||||
loop: true,
|
||||
// 静音
|
||||
muted: true,
|
||||
// 适应方式
|
||||
fit: 'contain',
|
||||
// 圆角
|
||||
borderRadius: 10,
|
||||
dataSource: [],
|
||||
selectedDataSource: '',
|
||||
videoTitle: '',
|
||||
videoIndex: '1',
|
||||
/** 外层css缩放比例 */
|
||||
sca: 1,
|
||||
showBtn: true,
|
||||
isOldStyle: true
|
||||
}
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key = yushiVideoConfig.key
|
||||
public chartConfig = cloneDeep(yushiVideoConfig)
|
||||
public option = cloneDeep(option)
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<collapse-item name="视频" expanded>
|
||||
<setting-item-box name="源" alone>
|
||||
<!-- <setting-item name="自定义源">
|
||||
<n-input v-model:value="optionData.dataset" size="small"></n-input>
|
||||
</setting-item> -->
|
||||
<setting-item name="index">
|
||||
<n-input v-model:value="optionData.videoIndex" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<n-checkbox v-model:checked="optionData.showTree" size="small">树状结构</n-checkbox>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
|
||||
<setting-item-box name="控制">
|
||||
<setting-item>
|
||||
<n-checkbox v-model:checked="optionData.loop" size="small">循环播放</n-checkbox>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<n-checkbox v-model:checked="optionData.muted" size="small">静音</n-checkbox>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
|
||||
<setting-item-box name="样式">
|
||||
<setting-item name="类型">
|
||||
<n-select v-model:value="optionData.fit" size="small" :options="fitList"></n-select>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<n-checkbox v-model:checked="optionData.isOldStyle" size="small">样式切换</n-checkbox>
|
||||
</setting-item>
|
||||
</setting-item-box>
|
||||
</collapse-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { option } from './config'
|
||||
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
|
||||
// 适应类型
|
||||
const fitList = [
|
||||
{
|
||||
value: 'fill',
|
||||
label: 'fill'
|
||||
},
|
||||
{
|
||||
value: 'contain',
|
||||
label: 'contain'
|
||||
},
|
||||
{
|
||||
value: 'cover',
|
||||
label: 'cover'
|
||||
},
|
||||
{
|
||||
value: 'scale-down',
|
||||
label: 'scale-down'
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label: 'none'
|
||||
}
|
||||
]
|
||||
|
||||
defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<typeof option>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
@ -0,0 +1,14 @@
|
||||
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
|
||||
import { ChatCategoryEnum, ChatCategoryEnumName } from '../..'
|
||||
|
||||
export const yushiVideoConfig: ConfigType = {
|
||||
key: 'yushiVideoHaz',
|
||||
chartKey: 'VYushiVideoHaz',
|
||||
conKey: 'VCYushiVideoHaz',
|
||||
title: '宇视摄像头',
|
||||
category: 'HazardousChemicalsSpace',
|
||||
categoryName: '危化品场景',
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
chartFrame: ChartFrameEnum.ECHARTS,
|
||||
image: 'video.png'
|
||||
}
|
@ -0,0 +1,794 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<template>
|
||||
<div class="go-border-box">
|
||||
<img src="./assets/title.svg" class="svg" />
|
||||
<div class="header-title">实时视频</div>
|
||||
<div :class="option.isOldStyle ? 'video_title' : 'video_title video_title_new'" v-if="option.showBtn">
|
||||
<div class="title_text" v-if="option.showTree && option.showBtn">{{ option.videoTitle }}</div>
|
||||
<n-select
|
||||
v-if="!option.showTree"
|
||||
class="video_select"
|
||||
placement="top-end"
|
||||
v-model:value="option.selectedDataSource"
|
||||
:options="option.dataSource"
|
||||
:style="`width:${w / 2}px;`"
|
||||
@update:value="handleSelectDataSource"
|
||||
/>
|
||||
<n-button
|
||||
tertiary
|
||||
v-else-if="option.showTree && option.showBtn && option.isOldStyle"
|
||||
:class="option.isOldStyle ? 'videoChangBtn' : 'videoChangBtn videoChangBtn_new'"
|
||||
@click="handleClick"
|
||||
>切换</n-button
|
||||
>
|
||||
<n-button
|
||||
tertiary
|
||||
v-else-if="option.showTree && option.showBtn && !option.isOldStyle"
|
||||
:class="option.isOldStyle ? 'videoChangBtn' : 'videoChangBtn videoChangBtn_new'"
|
||||
@click="handleClick"
|
||||
></n-button>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showDialog"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
title="选择摄像头"
|
||||
:class="['custom-tab-modal']"
|
||||
:draggable="{ bounds: 'none' }"
|
||||
:style="{ width: '644px', height: '420px' }"
|
||||
>
|
||||
<n-tree-select
|
||||
ref="cameraTree"
|
||||
class="cameraTree"
|
||||
:menu-props="{
|
||||
class: 'custom-dropdown'
|
||||
}"
|
||||
v-model:value="option.selectedDataSource"
|
||||
:options="option.dataset.list"
|
||||
clearable
|
||||
:default-expanded-keys="option.expandedKeys"
|
||||
style="width: 615px; margin-top: 16px"
|
||||
:show="isDropdownOpen"
|
||||
@update:show="handleShowChange"
|
||||
@update:value="handleSelectDataSource"
|
||||
/>
|
||||
</n-modal>
|
||||
</div>
|
||||
<div ref="vYushiVideoRef" class="go-video" :id="uuid"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//@ts-nocheck
|
||||
import { PropType, toRefs, shallowReactive, watch, ref, onDeactivated, onMounted, nextTick } from 'vue'
|
||||
import { useChartDataFetch } from '@/hooks'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
|
||||
import { option as configOption } from './config'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { md5 } from 'js-md5'
|
||||
import { PageEnum } from '@/enums/pageEnum'
|
||||
import axios from 'axios'
|
||||
import { getUUID, isPreview } from '@/utils'
|
||||
import { useYushiVideoStore } from '@/store/modules/yushiVideoStore/yushiVideoStore'
|
||||
|
||||
const yushiStore = useYushiVideoStore()
|
||||
let selectedList = []
|
||||
|
||||
const cameraTree = ref(null)
|
||||
const uuid = getUUID()
|
||||
let keepAliveInterval: any = null
|
||||
// const expandedKeys = ref([]);
|
||||
const isDropdownOpen = ref(false)
|
||||
const props = defineProps({
|
||||
chartConfig: {
|
||||
type: Object as PropType<CreateComponentType>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const { w, h } = toRefs(props.chartConfig.attr)
|
||||
let option = shallowReactive({ ...configOption, expandedKeys: [] })
|
||||
const showModalRef = ref(false)
|
||||
//@ts-ignore
|
||||
const findParentPath = (targetKey, nodes, path = []) => {
|
||||
for (const node of nodes) {
|
||||
if (node) {
|
||||
// 当前路径快照
|
||||
const currentPath = [...path, { key: node.key, label: node.label }]
|
||||
|
||||
console.log(node)
|
||||
// 找到目标节点时返回完整路径(排除自身)
|
||||
if (node.key === targetKey) return [...path, { key: node.key, label: node.label }]
|
||||
|
||||
// 递归搜索子节点
|
||||
if (node.children && node.children.length > 0) {
|
||||
//@ts-ignore
|
||||
const result = findParentPath(targetKey, node.children, currentPath)
|
||||
if (result) return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return null // 未找到
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const handleShowChange = value => {
|
||||
// Always set the dropdown to be open
|
||||
if (!value) {
|
||||
isDropdownOpen.value = true
|
||||
}
|
||||
}
|
||||
const setNode = (treeData: any) => {
|
||||
for (let i in treeData) {
|
||||
if (treeData[i].grade === 'org') {
|
||||
treeData[i].isLeaf = false
|
||||
treeData[i].disabled = true
|
||||
setNode(treeData[i].children)
|
||||
} else {
|
||||
treeData[i].isLeaf = true
|
||||
treeData[i].disabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let showDialog = showModalRef
|
||||
const handleClick = async () => {
|
||||
showModalRef.value = true
|
||||
setTimeout(async () => {
|
||||
//@ts-ignore
|
||||
// cameraTree.value.focus();
|
||||
setNode(option.dataset.list)
|
||||
isDropdownOpen.value = true
|
||||
//@ts-ignore
|
||||
if (option.selectedDataSource === '') {
|
||||
option.selectedDataSource = option.dataset.defaultValue
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
scrollIntoView(option.selectedDataSource)
|
||||
}, 200)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const scrollIntoView = (key: any) => {
|
||||
if (!cameraTree.value) return
|
||||
|
||||
const targetElement = document.getElementsByClassName('v-binder-follower-content')
|
||||
if (targetElement) {
|
||||
//@ts-ignore
|
||||
targetElement[0].style.transform = 'translateX(14px) translateY(104px)'
|
||||
// targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultSelectedCamera = (list, index) => {
|
||||
for (let i in list) {
|
||||
if (list[i] && list[i].grade === 'camera') {
|
||||
if (selectedList.length < Number(index)) {
|
||||
selectedList.push(list[i].key)
|
||||
}
|
||||
} else if (list[i]) {
|
||||
getDefaultSelectedCamera(list[i].children, index)
|
||||
}
|
||||
}
|
||||
|
||||
return selectedList[Number(index) - 1]
|
||||
}
|
||||
|
||||
// 预览更新
|
||||
const vYushiVideoRef = ref(null)
|
||||
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
option.dataset = newData
|
||||
})
|
||||
|
||||
// 编辑更新
|
||||
watch(
|
||||
() => props.chartConfig.option,
|
||||
(newData: any) => {
|
||||
option = newData
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
// 组织联动放开
|
||||
watch(
|
||||
() => yushiStore.$state.currentCameraOrgTree,
|
||||
(newData: any) => {
|
||||
option.dataset.list = newData
|
||||
//切换时同时设置默认摄像头
|
||||
selectedList = []
|
||||
option.selectedDataSource = getDefaultSelectedCamera(newData, option.videoIndex)
|
||||
if (newData) {
|
||||
let path = findParentPath(option.selectedDataSource, newData)
|
||||
props.chartConfig.option.videoTitle = path[1].label + '(' + path[path.length - 1].label + ')'
|
||||
props.chartConfig.option.expandedKeys = path.map((item: any) => {
|
||||
return item.key
|
||||
})
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
window.imosPlayer.playLive(window['_iframeId' + option.videoIndex], {
|
||||
camera: option.selectedDataSource,
|
||||
//@ts-ignore
|
||||
title: option.selectedDataSource
|
||||
})
|
||||
},
|
||||
{
|
||||
immediate: false,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
//宇视摄像头
|
||||
|
||||
const keepalive = (token: any) => {
|
||||
let ipaddr = PageEnum.VMIP
|
||||
let linkPort = PageEnum.VM_PORT
|
||||
let VIIDPort = PageEnum.VIID_PORT
|
||||
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `http://${ipaddr}${VIIDPort && ':' + VIIDPort}/VIID/hadesadapter/user/keepalive`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8',
|
||||
Authorization: token
|
||||
},
|
||||
responseType: 'json'
|
||||
}).then(res => {
|
||||
if (res.data.ErrCode === 404) {
|
||||
// VM B3351以前的用法
|
||||
//@ts-ignore
|
||||
const kaCLose = e => {
|
||||
console.warn('user dead', e)
|
||||
}
|
||||
let ws = new WebSocket(`ws://${ipaddr}${VIIDPort && ':' + VIIDPort}/VIID/event`)
|
||||
ws.onopen = () => {
|
||||
ws.send(token)
|
||||
}
|
||||
ws.onclose = kaCLose
|
||||
ws.onerror = kaCLose
|
||||
} else {
|
||||
// VM B3351以后的用法
|
||||
// let reConnect = 0
|
||||
let time = setInterval(() => {
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `http://${ipaddr}${VIIDPort && ':' + VIIDPort}/VIID/hadesadapter/user/keepalive`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8',
|
||||
Authorization: token
|
||||
},
|
||||
responseType: 'json'
|
||||
}).then(async res2 => {
|
||||
if (res2.data.ErrCode !== 0) {
|
||||
// reConnect++
|
||||
// if (reConnect > 5) {
|
||||
clearInterval(time)
|
||||
await initplayer(newData)
|
||||
// reConnect = 0
|
||||
console.warn('user dead')
|
||||
|
||||
// }
|
||||
} else {
|
||||
// reConnect = 0
|
||||
}
|
||||
})
|
||||
}, 30000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getRandom = () => {
|
||||
function S4() {
|
||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
|
||||
}
|
||||
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const initplayer = async (newData: any) => {
|
||||
const password = PageEnum.VM_PASS
|
||||
const username = PageEnum.VM_NAME
|
||||
let ipaddr = PageEnum.VMIP
|
||||
let linkPort = PageEnum.VM_PORT
|
||||
let VIIDPort = PageEnum.VIID_PORT
|
||||
|
||||
let date = new Date()
|
||||
let day = ('0' + date.getDate()).slice(-2)
|
||||
let month = ('0' + (date.getMonth() + 1)).slice(-2)
|
||||
let today = date.getFullYear() + '-' + month + '-' + day
|
||||
|
||||
//@ts-ignore
|
||||
let liveNetProtocol = undefined
|
||||
//@ts-ignore
|
||||
if (liveNetProtocol === undefined) {
|
||||
liveNetProtocol = 'tcp'
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
let replayNetProtocol = undefined
|
||||
if (replayNetProtocol === undefined) {
|
||||
replayNetProtocol = 'tcp'
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
let liveByMS = true
|
||||
|
||||
let nbsp = String.fromCharCode(160)
|
||||
//@ts-ignore
|
||||
let arr = []
|
||||
arr.length = 40
|
||||
//@ts-ignore
|
||||
let str = arr.fill(nbsp).join('')
|
||||
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `http://${ipaddr}${VIIDPort && ':' + VIIDPort}/VIID/login/v2`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8'
|
||||
},
|
||||
responseType: 'json'
|
||||
})
|
||||
.then(res => {
|
||||
const AccessCode = res.data.AccessCode //res.body改为res.data
|
||||
let usernameEncrypted = Base64.encode(username)
|
||||
let passwordEncrypted = md5(password)
|
||||
const loginStr = usernameEncrypted + AccessCode + passwordEncrypted
|
||||
const LoginSignature = md5(loginStr)
|
||||
axios({
|
||||
method: 'POST',
|
||||
url: `http://${ipaddr}${VIIDPort && ':' + VIIDPort}/VIID/login/v2`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf8'
|
||||
},
|
||||
responseType: 'json',
|
||||
data: {
|
||||
UserName: username,
|
||||
AccessCode,
|
||||
LoginSignature
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
// 悬浮方案要求网页title唯一,参照接口文档【悬浮播放器概念】
|
||||
document.title = document.title + str + getRandom()
|
||||
|
||||
const token = res.data.AccessToken
|
||||
//@ts-ignore
|
||||
window.token = res.data.AccessToken
|
||||
// if (option.videoIndex === '1') {
|
||||
keepalive(token)
|
||||
// }
|
||||
|
||||
// 跨nat映射时,如果对接端口不是一一映射到8093,则本接口必调。
|
||||
// 或https代理的对接端口不是443,本接口必调
|
||||
// 本接口必须在init之前调用
|
||||
|
||||
//@ts-ignore
|
||||
window.imosPlayer.setLinkPort(linkPort)
|
||||
|
||||
//@ts-ignore
|
||||
window.imosPlayer
|
||||
.init({
|
||||
ip: ipaddr, // 必传
|
||||
token: token, // 必传
|
||||
title: document.title, // 必传
|
||||
offset: [0, 0]
|
||||
})
|
||||
//@ts-ignore
|
||||
.then(async res => {
|
||||
//@ts-ignore
|
||||
if (res.ErrCode === 0) {
|
||||
//@ts-ignore
|
||||
window.imosPlayer.cssScale(option.sca)
|
||||
//@ts-ignore
|
||||
window.imosPlayer.setLiveNetLinkMode(liveNetProtocol, liveByMS)
|
||||
// 国产电脑坐标计算
|
||||
// window.imosPlayer.setWindowParams(windowParams);
|
||||
|
||||
//@ts-ignore
|
||||
window.__login = true
|
||||
//console.log('登录成功请创建窗格(建议打开F12方便查看运行结果)')
|
||||
|
||||
// await getDataSource(newData)
|
||||
//创建视频窗格
|
||||
//@ts-ignore
|
||||
let videoDom = await window.imosPlayer.createPanelWindow()
|
||||
// 窗格默认400*400,通过样式改宽高
|
||||
videoDom.style.width = '100%'
|
||||
videoDom.style.height = '100%'
|
||||
//@ts-ignore
|
||||
window.imosPlayer
|
||||
.setVoidClassName(videoDom, {
|
||||
className: 'basic'
|
||||
})
|
||||
//@ts-ignore
|
||||
.then(e => {
|
||||
console.log(e)
|
||||
})
|
||||
//@ts-ignore
|
||||
//放开后视频播放被覆盖,黑屏看不到
|
||||
window.imosPlayer.setVoidBroadCastRegion({
|
||||
voidClassName: 'basic',
|
||||
region: {
|
||||
times: 1000,
|
||||
class: ['n-modal-body-wrapper']
|
||||
}
|
||||
})
|
||||
// let array = document.getElementsByClassName('go-video')
|
||||
//@ts-ignore
|
||||
// window.imosPlayer.setViewDomByClassName({
|
||||
// className: 'basic',
|
||||
// doms: array
|
||||
// })
|
||||
|
||||
//@ts-ignore
|
||||
window['_iframeId' + option.videoIndex] = videoDom.id
|
||||
|
||||
let divDom = document.getElementById(uuid)
|
||||
//@ts-ignore
|
||||
divDom.style.width = '100%'
|
||||
//@ts-ignore
|
||||
divDom.style.height = '84%'
|
||||
//@ts-ignore
|
||||
if (divDom.children.length === 0) {
|
||||
//@ts-ignore
|
||||
divDom.appendChild(videoDom)
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
window.imosPlayer.playLive(videoDom.id, {
|
||||
camera: option.dataset.defaultValue,
|
||||
//@ts-ignore
|
||||
title: option.dataset.defaultValue
|
||||
})
|
||||
} else {
|
||||
//console.log(res.ErrMsg)
|
||||
}
|
||||
})
|
||||
//@ts-ignore
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('登录失败!!!')
|
||||
})
|
||||
})
|
||||
.catch(() => console.log('登录失败!!!'))
|
||||
}
|
||||
|
||||
const handleSelectDataSource = (v: any) => {
|
||||
showModalRef.value = false
|
||||
option.selectedDataSource = v
|
||||
let path = findParentPath(v, option.dataset.list)
|
||||
option.videoTitle = path[1].label + '(' + path[path.length - 1].label + ')'
|
||||
option.expandedKeys = path.map((item: any) => {
|
||||
return item.key
|
||||
})
|
||||
|
||||
//@ts-ignore
|
||||
window.imosPlayer.playLive(window['_iframeId' + option.videoIndex], {
|
||||
camera: v,
|
||||
//@ts-ignore
|
||||
title: v
|
||||
})
|
||||
}
|
||||
|
||||
const getDataSource = (newData: any) => {
|
||||
// 查相机编码
|
||||
let data = {
|
||||
org: 'iccsid',
|
||||
condition: {
|
||||
ItemNum: 3,
|
||||
Condition: [
|
||||
{
|
||||
QueryType: 256,
|
||||
LogicFlag: 0,
|
||||
QueryData: '1001'
|
||||
},
|
||||
{
|
||||
QueryType: 257,
|
||||
LogicFlag: 0,
|
||||
QueryData: '1'
|
||||
},
|
||||
{
|
||||
QueryType: 1,
|
||||
LogicFlag: 5,
|
||||
QueryData: ''
|
||||
}
|
||||
],
|
||||
QueryCount: 1,
|
||||
PageFirstRowNumber: 0,
|
||||
PageRowNum: 200
|
||||
}
|
||||
}
|
||||
let conditionEncodeStr1 = encodeURIComponent(JSON.stringify(data.condition))
|
||||
let url = `http://${PageEnum.VMIP}:${PageEnum.VIID_PORT}/VIID/query?org=${data.org}&condition=${conditionEncodeStr1}`
|
||||
|
||||
//@ts-ignore
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
headers: {
|
||||
//@ts-ignore
|
||||
Authorization: window.token
|
||||
},
|
||||
contentType: 'application/json'
|
||||
})
|
||||
//@ts-ignore
|
||||
.then(res => {
|
||||
//@ts-ignore
|
||||
if (res.data.ErrCode === 0) {
|
||||
//@ts-ignore
|
||||
let infoList = res.data.Result.InfoList
|
||||
console.log(infoList)
|
||||
//@ts-ignore
|
||||
let arr = []
|
||||
//@ts-ignore
|
||||
infoList.forEach(info => {
|
||||
//@ts-ignore
|
||||
if (info.ResItemV1.ResStatus === 1) {
|
||||
// 不要离线的
|
||||
arr.push({
|
||||
//@ts-ignore
|
||||
value: info.ResItemV1.ResCode,
|
||||
//@ts-ignore
|
||||
label: info.ResItemV1.ResName
|
||||
})
|
||||
}
|
||||
})
|
||||
//@ts-ignore
|
||||
option.dataSource = arr
|
||||
//@ts-ignore
|
||||
option.selectedDataSource = arr[0] ? arr[0].value : ''
|
||||
} else {
|
||||
console.log(`查询相机失败:[${res.data.ErrCode}] ${res.data.ErrMsg}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (isPreview()) {
|
||||
yushiStore.setCurrentCameraOrgTree(yushiStore.getCameraOrgTree)
|
||||
|
||||
await initplayer(props.chartConfig.option.dataset)
|
||||
if (props.chartConfig.option.dataset.list) {
|
||||
//加载title
|
||||
let path = findParentPath(props.chartConfig.option.dataset.defaultValue, props.chartConfig.option.dataset.list)
|
||||
props.chartConfig.option.videoTitle = path[1].label + '(' + path[path.length - 1].label + ')'
|
||||
props.chartConfig.option.expandedKeys = path.map((item: any) => {
|
||||
return item.key
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
//@ts-ignore
|
||||
clearInterval(keepAliveInterval)
|
||||
})
|
||||
|
||||
const dataSetHandle = async (newData: any) => {
|
||||
await initplayer(newData)
|
||||
}
|
||||
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
//加载title
|
||||
if (newData.list) {
|
||||
let path = findParentPath(newData.dataset.defaultValue, newData.list)
|
||||
props.chartConfig.option.videoTitle = path[1].label + '(' + path[path.length - 1].label + ')'
|
||||
props.chartConfig.option.expandedKeys = path.map((item: any) => {
|
||||
return item.key
|
||||
})
|
||||
dataSetHandle(newData)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@include go('video') {
|
||||
display: block;
|
||||
object-fit: v-bind('option.fit');
|
||||
// width: 100%;
|
||||
// height: v-bind('h * 0.9');
|
||||
}
|
||||
|
||||
.video_title {
|
||||
margin: 8px 8px;
|
||||
display: flex;
|
||||
color: #fff;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.video_title_new {
|
||||
background: #0d4b61;
|
||||
padding: 4px 4px 4px 12px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.videoChangBtn {
|
||||
padding: 4px 4px;
|
||||
border: 1px solid rgb(24, 160, 219);
|
||||
background: transparent;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.videoChangBtn_new {
|
||||
width: 52px;
|
||||
height: 23px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAXCAIAAADLIvskAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAGnUlEQVRIiW1XS3NcVxH+uvvcx0jRPGRZD0uW5QdWBJbLjo2DiwpkQYUN5QVlqigIBSyo7Pk17NiEDSkqVAoHCiohBEIMmBSxg4itRB7JDyW2rLFGM/LMnbm3WZxz7lyNc2sW5/Y9p0/3118/hiZ/+HMAIIIqE2WqgBUQALVCAAW5qhJIoSDAiQePPQioPaEKZrJ61EoVxOS/AwABINLi1aq9J3vGxCPJ7mMrzZ66CEBm7ydCpkogZ5A1j6AK2n/ESQaGZ0Sq+pRyt19ViYalJowlCExYKvX3dpHfoFCHozoMVGFRZDDUqgOs9x5RD7Mz3b/mSJAqQHD6lKwLLggOudwyVUgYQjNj4hKJ8WdIFUzOGyp4rwr2xwt+ug0KWFYMwe79Gg6H+s35Ry1oJkDCME37JohLLGI/OyYBbBFx5HE0kjAqVWutB5tfeKWHhiSKCdrrdCw8pUoNwJNmgwpBLGoo3DOwXoJQej1jophEnEEAgyyPGYPQEJFCp5dOV+cXVt78TXny0NjkjHrqWS70O52tT1YAWnj+BQC333vbHj//8ivXX381aTe1sJksEI5t5N1Tyyci4iBk0zUmipnFOmLtsqHign8ueEwEZjEj1fHq/LFSpRaXK407dUBLlfG4XH20dguwZIR1+NjFF01cOv3dHwEwUbz7YPPDX/8yU5dVHjwFyKa+5TqITBCmYoyJS2QMcnoS5VQm7BMSC5hI5FF9dau+WptbOPK1b9780xsEVOYWTrz4bRI5c/nHcaUK4JnJmfrVv0yfeu7aq7/o7O6YMD7/8ivrV98lES6ybD95c7JIGLIJjAkjZhkkzvDCpdjM6a9W5xaeOTh19vJPonLlo9+9RswAiTFQMHO/2xUxN377q/kL3wB0/R/vnvvBzz7/3/Xa/DEAE8ef7Xc7pWot7ff2Hn7mM1x9HpB6IoEUIDYBiRgTRSzGfs+d2LcgQDFaqxFRd3dn7e9vp91OZ3dn9OjJNOkSCwASISLHXSaAWMz9D69NLi03722MTEyZKGrUP5k4sVSqjt9pbKk6nHgoT32VlCBgMUbCiEQsCTSH1wUfufz2++9MnjxVPnR4b3uLCCwSxKW0l7AxqkrMIHJ6iEcOHDzy/Avd3Wbz/t2Nf78/tXgqGqvkC7BQAYIiufOKziZgI0aCiI2xOcSkqmBQ5hPKdwOACMxgYhGrIK5Uu+1dFlHVuFJLk25YGll86dLI+GTSan628p9otAwiFpG4RPYgs13kVFZVEFht+wJ5JjrkTBgyS5GYAPJ3W0RcA2MmEBsze+bC9vra6IGp7Y1PycLAnPaSLE0bG7f3trf6ScLM40dPmDAiovLMXD/pzp//eml8woRRde5+c/OeS7gvygYAYgJiMRKGJPJUTc1TvEBB4niscvZ7P+20mo/vrpdnZjeu/c0W8MBj8+DjG4fOXCDmtNffubtuQxWPVRS6c7eetFsgSvv9QtkfJrrjoglYxEgQ2q3Y121scSwWbR2dmCyNT2xev/b5zRuHls837tR7T/ZsEpgoTtptEgGIiAHtNBudnQYIU88uK5C0W0S8dfumUyfiDRoebBznAkMshoOARKAEcn29iFpxZNiurz689d+k3Tp4fGlqaXnlzdfL04eJtNtuTRxfvH/jAxaj6mowi5EgnDn9XHX2yM23rkB18VvfGZue3fzog6Td8kAMpouhhyUgj5zB/pAPRdY+3eaOAuXp2blzF1f//Ps0eVL78nJl9giApNV6vLEWxKXFly5JGG3886+Hz10cP/qlh7dWPv7jG2kvIWD1nT/MfOXsqUvfX7nyWm4fP3WrD6thZrp89dGtt664+jKIqxb7oJ8qKcc86/dd2x3URwUQlkbTfi/rJWZkNOv10l5SuJMASBCkvd6gbg2Swd7ozJg4frKxUTcSBOwYoG7GGgyT4JxxsNMvCNBMQcyMgXrvVD/pAoCYtNuxwVU/ITqns8xT3F03PFdZqRhiNmwM+YSgfQMaADt1kULJTgwoNuzBq3oTfbfLp/r95nu1dnAstn8/kbj2z2JI2LAEfipRFDTkl8FX7bxm5p2EaPCWLx0TCilY0OnmMT/jD4qo7RPsHFU2hogNWCqHj/p/Bu5hHjiRGwE/pxD8bA0IuW1WkikUbnrP4NeAevKqv0d9s9ICCjmv4+oBEJu1f71XjClbVAkECIEJAgjBEIQQEAwhIISEgBAwQkJECBmRgIFuhiRDV5FkSBS9DD11vxTo24X/ZYrU2g1owTEC2vVPG/c2/g+61DE71azopwAAAABJRU5ErkJggg==');
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.custom-tab-modal > .n-card-header {
|
||||
background-color: rgba(26, 56, 113, 1) !important;
|
||||
background-image: linear-gradient(to right, rgba(8, 100, 177, 0.7), transparent) !important;
|
||||
padding: 16px !important;
|
||||
border: 2px solid rgba(62, 200, 244, 1);
|
||||
border-bottom-width: 0px !important;
|
||||
}
|
||||
|
||||
.custom-tab-modal > .n-card__content {
|
||||
background-color: rgba(26, 56, 113, 1) !important;
|
||||
border: 2px solid rgba(62, 200, 244, 1);
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .n-button {
|
||||
--normal-border: #115f8c;
|
||||
border-radius: 0;
|
||||
background: linear-gradient(to left, var(--normal-border), var(--normal-border)) left top no-repeat,
|
||||
linear-gradient(to bottom, var(--normal-border), var(--normal-border)) left top no-repeat,
|
||||
linear-gradient(to left, var(--normal-border), var(--normal-border)) right top no-repeat,
|
||||
linear-gradient(to bottom, var(--normal-border), var(--normal-border)) right top no-repeat,
|
||||
linear-gradient(to left, var(--normal-border), var(--normal-border)) left bottom no-repeat,
|
||||
linear-gradient(to bottom, var(--normal-border), var(--normal-border)) left bottom no-repeat,
|
||||
linear-gradient(to left, var(--normal-border), var(--normal-border)) right bottom no-repeat,
|
||||
linear-gradient(to left, var(--normal-border), var(--normal-border)) right bottom no-repeat;
|
||||
background-size: 0.1rem 0.5rem, 0.5rem 0.1rem, 0.1rem 0.5rem, 0.5rem 0.1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .n-button:hover {
|
||||
--active-border: #00e4ff;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(to left, var(--active-border), var(--active-border)) left top no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) left top no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right top no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) right top no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) left bottom no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) left bottom no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right bottom no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right bottom no-repeat;
|
||||
background-size: 0.1rem 0.5rem, 0.5rem 0.1rem, 0.1rem 0.5rem, 0.5rem 0.1rem;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .active-button {
|
||||
--active-border: #00e4ff;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(to left, var(--active-border), var(--active-border)) left top no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) left top no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right top no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) right top no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) left bottom no-repeat,
|
||||
linear-gradient(to bottom, var(--active-border), var(--active-border)) left bottom no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right bottom no-repeat,
|
||||
linear-gradient(to left, var(--active-border), var(--active-border)) right bottom no-repeat;
|
||||
background-size: 0.1rem 0.5rem, 0.5rem 0.1rem, 0.1rem 0.5rem, 0.5rem 0.1rem;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .button_content {
|
||||
width: 126px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
background-color: rgba(10, 166, 254, 0.3) !important;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .n-button:hover .button_content {
|
||||
background-color: rgba(0, 228, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.custom-tab-modal__tab .active-button .button_content {
|
||||
background-color: rgba(0, 228, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.custom-data-table .n-data-table-th,
|
||||
.custom-data-table .n-data-table-thead,
|
||||
.custom-data-table .n-data-table-table,
|
||||
.custom-data-table .n-data-table-tr:not(.n-data-table-tr--summary):hover {
|
||||
background-color: rgba(60, 124, 211, 0.15) !important;
|
||||
}
|
||||
|
||||
.custom-data-table tbody td:nth-child(odd),
|
||||
.custom-data-table .n-data-table-td {
|
||||
background-color: rgba(60, 124, 211, 0.05) !important;
|
||||
}
|
||||
|
||||
.custom-data-table .n-data-table-tr.n-data-table-tr--striped {
|
||||
background-color: rgba(60, 124, 211, 0.15) !important;
|
||||
}
|
||||
|
||||
.custom-data-table .operation {
|
||||
color: rgb(0, 228, 255) !important;
|
||||
}
|
||||
|
||||
.custom-data-table .n-data-table-th__title {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.custom-dropdown {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: unset !important;
|
||||
width: 615px !important;
|
||||
}
|
||||
.custom-data-table .v-binder-follower-content {
|
||||
transform: translateX(7px) translateY(104px) !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
@include go(border-box) {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
// 不渐变
|
||||
// background-color: #0E121B;
|
||||
// 渐变
|
||||
background: linear-gradient(to top,
|
||||
rgba(14, 18, 27, 1) 0%,
|
||||
rgba(14, 18, 27, 0.6) 100%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
/* 边框宽度 */
|
||||
background: linear-gradient(to top,
|
||||
rgba(128, 128, 128, 0.3),
|
||||
rgba(128, 128, 128, 0));
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff, #fff) content-box,
|
||||
linear-gradient(#fff, #fff);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
left: 80px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #eee;
|
||||
font-style: italic;
|
||||
text-shadow: 0 0 10px #00E5FF;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.svg {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
}
|
||||
</style>
|
100
src/packages/components/Charts/Pies/Pie/config.ts
Normal file
100
src/packages/components/Charts/Pies/Pie/config.ts
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
import { echartOptionProfixHandle, PublicConfigClass } from '@/packages/public'
|
||||
import { CreateComponentType } from '@/packages/index.d'
|
||||
import { PieConfig } from './index'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import dataJson from './data.json'
|
||||
|
||||
export const includes = ['legend']
|
||||
|
||||
|
||||
export const indicatorPlacements = [
|
||||
{
|
||||
label: '内部',
|
||||
value: 'inside'
|
||||
},
|
||||
{
|
||||
label: '外部',
|
||||
value: 'outside'
|
||||
}
|
||||
]
|
||||
|
||||
export enum FontWeightEnum {
|
||||
NORMAL = '常规',
|
||||
BOLD = '加粗',
|
||||
}
|
||||
|
||||
export const FontWeightObject = {
|
||||
[FontWeightEnum.NORMAL]: 'normal',
|
||||
[FontWeightEnum.BOLD]: 'bold',
|
||||
}
|
||||
|
||||
export enum LegendOrientEnum {
|
||||
HORIZONTAL = '水平',
|
||||
VERTICAL = '垂直'
|
||||
}
|
||||
|
||||
export const legendOrientObject = {
|
||||
[LegendOrientEnum.HORIZONTAL]: 'horizontal',
|
||||
[LegendOrientEnum.VERTICAL]: 'vertical'
|
||||
}
|
||||
|
||||
const option = {
|
||||
renderer: 'canvas',
|
||||
dataset: dataJson,
|
||||
labelStyleType: '样式一',
|
||||
labelColor1: '#ffffff',
|
||||
labelColor2: '#ffffff',
|
||||
labelPadding1: "0,3,0,-5",
|
||||
labelPadding2: "0,3,-10,5",
|
||||
showDataLabels: false,
|
||||
showLegendValue: false,
|
||||
showLegendPercent: false,
|
||||
showLableLineImage: false,
|
||||
labelLineColor: '#ffffff',
|
||||
lableLineLength1: 30,
|
||||
lableLineLength2: 40,
|
||||
series: [],
|
||||
legend: {
|
||||
itemGap: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
orient: 'vertical',
|
||||
// top: "center",
|
||||
//@ts-ignore
|
||||
data: dataJson,
|
||||
icon: 'rectangle',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14,
|
||||
padding: [5, 0],
|
||||
}
|
||||
},
|
||||
grid3D: {
|
||||
show: false,
|
||||
boxHeight: 3, //圆环的高度
|
||||
//@ts-ignore
|
||||
left: '-15%',
|
||||
//@ts-ignore
|
||||
top: '5%', //3d饼图的位置
|
||||
viewControl: {
|
||||
//3d效果可以放大、旋转等,请自己去查看官方配置
|
||||
alpha: 15, //角度
|
||||
distance: 280, //调整视角到主体的距离,类似调整zoom
|
||||
rotateSensitivity: 0, //设置为0无法旋转
|
||||
zoomSensitivity: 1, //设置为0无法缩放
|
||||
panSensitivity: 1, //设置为0无法平移
|
||||
autoRotate: false, //自动旋转
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export default class Config extends PublicConfigClass implements CreateComponentType {
|
||||
public key: string = PieConfig.key
|
||||
|
||||
public chartConfig = cloneDeep(PieConfig)
|
||||
|
||||
// 图表配置项
|
||||
public option = echartOptionProfixHandle(option, includes)
|
||||
}
|
122
src/packages/components/Charts/Pies/Pie/config.vue
Normal file
122
src/packages/components/Charts/Pies/Pie/config.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<!-- 默认展开 -->
|
||||
<global-setting :optionData="config"></global-setting>
|
||||
<CollapseItem name="柱状图3D" :expanded="true">
|
||||
<SettingItemBox name="三维配置">
|
||||
<SettingItem name="视距">
|
||||
<n-input-number v-model:value="config.grid3D!.viewControl.distance" size="small" :min="0" placeholder="视距"></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="Y轴">
|
||||
<n-input-number v-model:value="config.grid3D!.viewControl.alpha" size="small" :min="0" placeholder="y轴"></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="Z轴">
|
||||
<n-input-number v-model:value="config.grid3D!.boxHeight" size="small" :min="0" placeholder="Z轴"></n-input-number>
|
||||
</SettingItem>
|
||||
<!-- <SettingItem name="饼图大小">
|
||||
<n-input-number v-model:value="optionData.size" size="small" :min="0" placeholder="饼图大小"></n-input-number>
|
||||
</SettingItem> -->
|
||||
<!-- <SettingItem name="透明度">
|
||||
<n-input-number v-model:value="optionData.opacity" size="small" :min="0" :max="1" placeholder="透明度"></n-input-number>
|
||||
</SettingItem> -->
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="config.showDataLabels" size="small" />
|
||||
<n-text>显示指标线</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item name="指标线颜色" v-if="config.showDataLabels">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelLineColor"></n-color-picker>
|
||||
</setting-item>
|
||||
<SettingItem name="指标线长度1" v-if="config.showDataLabels">
|
||||
<n-input-number v-model:value="config.lableLineLength1" size="small" :min="0" placeholder="指标线长度1"></n-input-number>
|
||||
</SettingItem>
|
||||
<SettingItem name="指标线长度2" v-if="config.showDataLabels">
|
||||
<n-input-number v-model:value="config.lableLineLength2" size="small" :min="0" placeholder="指标线长度2"></n-input-number>
|
||||
</SettingItem>
|
||||
<setting-item name="指标样式类型">
|
||||
<n-select v-model:value="config.labelStyleType" size="small" :options="labelTypeOptions" />
|
||||
</setting-item>
|
||||
<setting-item name="指标文字颜色1" v-if="config.showDataLabels">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelColor1"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="指标文字颜色2" v-if="config.showDataLabels">
|
||||
<n-color-picker size="small" :modes="['hex']" v-model:value="config.labelColor2"></n-color-picker>
|
||||
</setting-item>
|
||||
<setting-item name="指标文字位置1" v-if="config.showDataLabels">
|
||||
<n-input v-model:value="config.labelPadding1" type="text" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item name="指标文字位置2" v-if="config.showDataLabels">
|
||||
<n-input v-model:value="config.labelPadding2" type="text" size="small"></n-input>
|
||||
</setting-item>
|
||||
<setting-item v-if="config.showDataLabels">
|
||||
<n-space>
|
||||
<n-switch v-model:value="config.showLableLineImage" size="small" />
|
||||
<n-text>指标线显示图片</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="config.showLegendValue" size="small" />
|
||||
<n-text>图例显示数值</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
<setting-item>
|
||||
<n-space>
|
||||
<n-switch v-model:value="config.showLegendPercent" size="small" />
|
||||
<n-text>图例显示百分比</n-text>
|
||||
</n-space>
|
||||
</setting-item>
|
||||
</SettingItemBox>
|
||||
</CollapseItem>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, PropType } from 'vue'
|
||||
// 以下是封装的设置模块布局组件,具体效果可在官网查看
|
||||
|
||||
// 获取 option 的数据,便于使用 typeof 获取类型
|
||||
import { FontWeightEnum, FontWeightObject, LegendOrientEnum, legendOrientObject } from './config'
|
||||
// 以下是封装的设置模块布局组件,具体效果可在官网查看
|
||||
import { GlobalSetting, CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
|
||||
import { GlobalThemeJsonType } from '@/settings/chartThemes'
|
||||
|
||||
const props = defineProps({
|
||||
optionData: {
|
||||
type: Object as PropType<GlobalThemeJsonType>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const fontWeightOptions = [
|
||||
{
|
||||
label: FontWeightEnum.NORMAL,
|
||||
value: FontWeightObject[FontWeightEnum.NORMAL]
|
||||
},
|
||||
{
|
||||
label: FontWeightEnum.BOLD,
|
||||
value: FontWeightObject[FontWeightEnum.BOLD]
|
||||
}
|
||||
]
|
||||
|
||||
const legendOrients = [
|
||||
{
|
||||
label: LegendOrientEnum.HORIZONTAL,
|
||||
value: legendOrientObject[LegendOrientEnum.HORIZONTAL]
|
||||
},
|
||||
{
|
||||
label: LegendOrientEnum.VERTICAL,
|
||||
value: legendOrientObject[LegendOrientEnum.VERTICAL]
|
||||
}
|
||||
]
|
||||
|
||||
const labelTypeOptions = [
|
||||
{ label: '样式一', value: '样式一' },
|
||||
{ label: '样式二', value: '样式二' },
|
||||
{ label: '样式三', value: '样式三' },
|
||||
|
||||
]
|
||||
|
||||
const config = computed(() => {
|
||||
return props.optionData
|
||||
})
|
||||
</script>
|
27
src/packages/components/Charts/Pies/Pie/data.json
Normal file
27
src/packages/components/Charts/Pies/Pie/data.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"name": "分类一",
|
||||
"value": 94,
|
||||
"itemStyle": { "color": "#9751c2" },
|
||||
"ratio": 0,
|
||||
"isSelected": true
|
||||
},
|
||||
{
|
||||
"name": "分类二",
|
||||
"value": 82,
|
||||
"itemStyle": { "color": "#199ed4" },
|
||||
"ratio": 0
|
||||
},
|
||||
{
|
||||
"name": "分类三",
|
||||
"value": 78,
|
||||
"itemStyle": { "color": "#06c1cd" },
|
||||
"ratio": 0
|
||||
},
|
||||
{
|
||||
"name": "分类四",
|
||||
"value": 60,
|
||||
"itemStyle": { "color": "#f7a35c" },
|
||||
"ratio": 0
|
||||
}
|
||||
]
|
23
src/packages/components/Charts/Pies/Pie/index.ts
Normal file
23
src/packages/components/Charts/Pies/Pie/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// 公共类型声明
|
||||
import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d'
|
||||
// 当前[信息模块]分类声明
|
||||
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
|
||||
|
||||
export const PieConfig: ConfigType = {
|
||||
// 唯一key
|
||||
key: 'Pie',
|
||||
// 图表组件渲染 Components 格式: V + key
|
||||
chartKey: 'VPie',
|
||||
// 配置组件渲染 Components 格式: VC + key
|
||||
conKey: 'VCPie',
|
||||
// 名称
|
||||
title: '3D环状饼图',
|
||||
// 子分类目录
|
||||
category: ChatCategoryEnum.PIE,
|
||||
// 子分类目录
|
||||
categoryName: ChatCategoryEnumName.PIE,
|
||||
// 包分类
|
||||
package: PackagesCategoryEnum.CHARTS,
|
||||
// 图片
|
||||
image: 'pie_3d.png'
|
||||
}
|
891
src/packages/components/Charts/Pies/Pie/index.vue
Normal file
891
src/packages/components/Charts/Pies/Pie/index.vue
Normal file
@ -0,0 +1,891 @@
|
||||
<template>
|
||||
<v-chart
|
||||
ref="vChartRef"
|
||||
autoresize
|
||||
:init-options="initOptions"
|
||||
:theme="themeColor"
|
||||
:update-options="{ notMerge: true }"
|
||||
:option="option.value"
|
||||
></v-chart>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import 'echarts-gl'
|
||||
import { toRaw, toReadonly, toRefs } from '@vue/reactivity'
|
||||
import { isPreview } from '@/utils'
|
||||
import { computed, onMounted, PropType, reactive, watch } from 'vue'
|
||||
import VChart from 'vue-echarts'
|
||||
import * as echarts from '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)
|
||||
|
||||
const chartEditStore = useChartEditStore()
|
||||
use([DatasetComponent, CanvasRenderer, PieChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent])
|
||||
const {
|
||||
legend,
|
||||
grid3D,
|
||||
showDataLabels,
|
||||
showLegendValue,
|
||||
showLegendPercent,
|
||||
showLableLineImage,
|
||||
labelLineColor,
|
||||
labelColor1,
|
||||
labelColor2,
|
||||
labelPadding1,
|
||||
labelPadding2,
|
||||
lableLineLength1,
|
||||
lableLineLength2,
|
||||
labelStyleType
|
||||
} = toRefs(props.chartConfig.option)
|
||||
|
||||
// const { dataset, alpha, beta, depth, size, showDataLabels, innerSize, opacity } = toRefs(props.chartConfig.option)
|
||||
|
||||
const option = reactive({
|
||||
value: {}
|
||||
})
|
||||
|
||||
// 字体自适应。
|
||||
const fontSize = (res: any) => {
|
||||
const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
|
||||
if (!clientWidth) return
|
||||
const fontSize = 40 * (clientWidth / 1920)
|
||||
console.log(fontSize)
|
||||
return res * fontSize
|
||||
}
|
||||
|
||||
const dataHandle = (newData: any) => {
|
||||
if (newData) {
|
||||
//@ts-ignore
|
||||
getPie3D(newData, newData[0].ratio)
|
||||
//是否需要label指引线,如果要就添加一个透明的2d饼状图并调整角度使得labelLine和3d的饼状图对齐,并再次setOption
|
||||
props.chartConfig.option.series.push({
|
||||
name: 'pie2d',
|
||||
type: 'pie',
|
||||
labelLine: {
|
||||
show: showDataLabels?.value || false,
|
||||
length: lableLineLength1?.value || 30,
|
||||
length2: lableLineLength2?.value || 30,
|
||||
lineStyle: {
|
||||
color: labelLineColor?.value || '#fff',
|
||||
width: 2
|
||||
}
|
||||
},
|
||||
label: {
|
||||
opacity: showDataLabels.value ? 1 : 0,
|
||||
show: true,
|
||||
formatter: showLableLineImage?.value
|
||||
? labelStyleType?.value === '样式一'
|
||||
? ' {c|{c}个}\n {img|}\n {a| 占比{d}%}'
|
||||
: labelStyleType?.value === '样式二'
|
||||
? ' {style|{c} 个}\n {img|}\n {e|{b}}'
|
||||
: ' {style|{d}%}\n {img|}\n {f|{b}}'
|
||||
: labelStyleType?.value === '样式一'
|
||||
? ' {c|{c}个}\n {dot|}\n {a| 占比{d}%}'
|
||||
: labelStyleType?.value === '样式二'
|
||||
? ' {style|{c} 个}\n {dot|}\n{e|{b}}'
|
||||
: ' {style|{d}%}\n {dot|}\n {f|{b}}',
|
||||
rich: {
|
||||
dot: {
|
||||
backgroundColor: '#fff', // 适应颜色
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderRadius: 5, // 保证是圆角
|
||||
fontSize: 16,
|
||||
padding: [5, -5, 5, -5] // 这里就保证是要给宽高6的正方形
|
||||
},
|
||||
img: {
|
||||
backgroundColor: { image: '/src/assets/images/chart/charts/dot.png' }, // 适应颜色
|
||||
width: 24,
|
||||
height: 24,
|
||||
// borderRadius: 5, // 保证是圆角
|
||||
// fontSize: 16,
|
||||
padding: [0, -30, 0, -15]
|
||||
},
|
||||
a: {
|
||||
fontSize: 14,
|
||||
color: labelColor2?.value || '#fff',
|
||||
align: 'left',
|
||||
padding: labelPadding2?.value.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
}) || [0, 3, 0, -5]
|
||||
},
|
||||
c: {
|
||||
fontSize: 20,
|
||||
color: labelColor1?.value || '#41a6fc',
|
||||
align: 'left',
|
||||
padding: labelPadding1?.value.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
}) || [0, 3, -10, 5]
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
color: labelColor1?.value || '#b8c276',
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: '#fefefe' }, // 开始颜色
|
||||
// { offset: 1, color: '#e0e98d' } // 结束颜色
|
||||
// ]),
|
||||
align: 'left',
|
||||
padding: labelPadding1?.value.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
}) || [0, 3, -10, 5]
|
||||
},
|
||||
e: {
|
||||
fontSize: 14,
|
||||
color: labelColor2?.value || '#fff',
|
||||
align: 'left',
|
||||
padding: labelPadding2?.value.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
}) || [0, 0, 0, 23]
|
||||
},
|
||||
f: {
|
||||
fontSize: 14,
|
||||
color: labelColor2?.value || '#fff',
|
||||
align: 'left',
|
||||
padding: labelPadding2?.value.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
}) || [0, 0, 0, 3]
|
||||
}
|
||||
}
|
||||
},
|
||||
silent: true,
|
||||
startAngle: 320, //起始角度,支持范围[0, 360]。
|
||||
clockwise: false, //饼图的扇区是否是顺时针排布。上述这两项配置主要是为了对齐3d的样式
|
||||
//@ts-ignore
|
||||
radius: [fontSize(0.6) + '%', fontSize(0.8) + '%'],
|
||||
//@ts-ignore
|
||||
center: [fontSize(1) + '%', fontSize(1.4) + '%'], //指示线的位置
|
||||
data: newData,
|
||||
itemStyle: {
|
||||
opacity: 0
|
||||
}
|
||||
})
|
||||
option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes)
|
||||
option.value = props.chartConfig.option
|
||||
}
|
||||
}
|
||||
|
||||
const getPie3D = (pieData: any, internalDiameterRatio: any) => {
|
||||
//internalDiameterRatio:透明的空心占比
|
||||
//@ts-ignore
|
||||
let series: any[] = []
|
||||
|
||||
let sumValue = 0
|
||||
let startValue = 0
|
||||
let endValue = 0
|
||||
let k = 1 - internalDiameterRatio
|
||||
pieData.sort((a: any, b: any) => {
|
||||
return b.value - a.value
|
||||
})
|
||||
// 为每一个饼图数据,生成一个 series-surface 配置
|
||||
for (let i = 0; i < pieData.length; i++) {
|
||||
sumValue += pieData[i].value
|
||||
let seriesItem = {
|
||||
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
|
||||
type: 'surface',
|
||||
parametric: true,
|
||||
wireframe: {
|
||||
show: false
|
||||
},
|
||||
pieData: pieData[i],
|
||||
itemStyle: pieData[i].itemStyle,
|
||||
pieStatus: {
|
||||
selected: false,
|
||||
hovered: false,
|
||||
k: k
|
||||
},
|
||||
radius: '20%',
|
||||
center: ['0%', '20%']
|
||||
}
|
||||
|
||||
series.push(seriesItem)
|
||||
}
|
||||
// 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
|
||||
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
endValue = startValue + series[i].pieData.value
|
||||
series[i].pieData.startRatio = startValue / sumValue
|
||||
series[i].pieData.endRatio = endValue / sumValue
|
||||
//@ts-ignore
|
||||
series[i].parametricEquation = getParametricEquation(
|
||||
series[i].pieData.startRatio,
|
||||
series[i].pieData.endRatio,
|
||||
series[i].pieData.isSelected ?? false,
|
||||
false,
|
||||
k,
|
||||
series[i].pieData.value
|
||||
)
|
||||
startValue = endValue
|
||||
}
|
||||
// let boxHeight = getHeight3D(series, 15); //通过传参设定3d饼/环的高度,26代表26px
|
||||
// 准备待返回的配置项,把准备好的 legendData、series 传入。
|
||||
// @ts-nocheck
|
||||
//@ts-ignore
|
||||
|
||||
props.chartConfig.option = {
|
||||
renderer: 'canvas',
|
||||
// labelLine: {
|
||||
// show: true,
|
||||
// length: 15,
|
||||
// length2: 15,
|
||||
// },
|
||||
dataset: pieData,
|
||||
showLableLineImage: showLableLineImage?.value || false,
|
||||
labelColor1: labelColor1?.value || '',
|
||||
labelColor2: labelColor2?.value || '',
|
||||
labelPadding1: labelPadding1?.value || '',
|
||||
labelPadding2: labelPadding2?.value || '',
|
||||
labelStyleType: labelStyleType?.value || '样式一',
|
||||
labelLineColor: labelLineColor?.value || '#fff',
|
||||
lableLineLength1: lableLineLength1?.value || 30,
|
||||
lableLineLength2: lableLineLength2?.value || 30,
|
||||
showLegendValue: showLegendValue?.value || false,
|
||||
showLegendPercent: showLegendPercent?.value || false,
|
||||
showDataLabels: showDataLabels?.value || false,
|
||||
legend: {
|
||||
show: legend.value.show,
|
||||
itemGap: legend.value.itemGap,
|
||||
x: legend.value.x,
|
||||
y: legend.value.y,
|
||||
orient: legend.value.orient,
|
||||
// top: "center",
|
||||
//@ts-ignore
|
||||
data: pieData,
|
||||
icon: 'rectangle',
|
||||
//@ts-ignore
|
||||
itemWidth: fontSize(0.3), //矩形宽度
|
||||
//@ts-ignore
|
||||
itemHeight: fontSize(0.25), //矩形高度
|
||||
//@ts-ignore
|
||||
// formatter: res => {
|
||||
// let str = ''
|
||||
// series.forEach(ele => {
|
||||
// if (res == ele.name) {
|
||||
// let bfb = ((ele.pieData.endRatio - ele.pieData.startRatio) * 100).toFixed(2)
|
||||
// str = ele.pieData.name
|
||||
|
||||
// if (showLegendValue.value) {
|
||||
// str += " " + ele.pieData.value
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return str
|
||||
// },
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: legend.value.textStyle.fontSize,
|
||||
padding: [5, 0]
|
||||
}
|
||||
},
|
||||
|
||||
//@ts-ignore
|
||||
tooltip: {
|
||||
show: true,
|
||||
//@ts-ignore
|
||||
formatter: params => {
|
||||
if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
|
||||
//@ts-ignore
|
||||
let bfb = (
|
||||
(series[params.seriesIndex].pieData.endRatio -
|
||||
//@ts-ignore
|
||||
series[params.seriesIndex].pieData.startRatio) *
|
||||
100
|
||||
).toFixed(2)
|
||||
return (
|
||||
`${params.seriesName}<br/>` +
|
||||
`<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
|
||||
`${bfb}%`
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxis3D: {
|
||||
min: -1,
|
||||
max: 1
|
||||
},
|
||||
yAxis3D: {
|
||||
min: -1,
|
||||
max: 1
|
||||
},
|
||||
zAxis3D: {
|
||||
min: -1,
|
||||
max: 1
|
||||
},
|
||||
grid3D: {
|
||||
show: false,
|
||||
boxHeight: grid3D.value.boxHeight, //圆环的高度
|
||||
//@ts-ignore
|
||||
left: '-15%',
|
||||
//@ts-ignore
|
||||
top: '5%', //3d饼图的位置
|
||||
bottom: '5%',
|
||||
viewControl: {
|
||||
//3d效果可以放大、旋转等,请自己去查看官方配置
|
||||
alpha: grid3D.value.viewControl.alpha, //角度
|
||||
distance: grid3D.value.viewControl.distance, //调整视角到主体的距离,类似调整zoom
|
||||
rotateSensitivity: 0, //设置为0无法旋转
|
||||
zoomSensitivity: 1, //设置为0无法缩放
|
||||
panSensitivity: 1, //设置为0无法平移
|
||||
autoRotate: false //自动旋转
|
||||
}
|
||||
// postEffect: {
|
||||
// //配置这项会出现锯齿,请自己去查看官方配置有办法解决
|
||||
// enable: true,
|
||||
// bloom: {
|
||||
// enable: true,
|
||||
// bloomIntensity: 0.1,
|
||||
// },
|
||||
// SSAO: {
|
||||
// enable: true,
|
||||
// quality: "medium",
|
||||
// radius: 2,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
series: series
|
||||
}
|
||||
}
|
||||
|
||||
const getParametricEquation = (startRatio: any, endRatio: any, isSelected: any, isHovered: any, k: any, h: any) => {
|
||||
// 计算
|
||||
let midRatio = (startRatio + endRatio) / 2
|
||||
let startRadian = startRatio * Math.PI * 2
|
||||
let endRadian = endRatio * Math.PI * 2
|
||||
let midRadian = midRatio * Math.PI * 2
|
||||
// 如果只有一个扇形,则不实现选中效果。
|
||||
if (startRatio === 0 && endRatio === 1) {
|
||||
isSelected = false
|
||||
}
|
||||
// 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
|
||||
k = typeof k !== 'undefined' ? k : 1 / 3
|
||||
// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
|
||||
let offsetX = isSelected ? Math.cos(midRadian) * 0.2 : 0
|
||||
let offsetY = isSelected ? Math.sin(midRadian) * 0.2 : 0
|
||||
let offsetZ = isSelected ? Math.sin(midRadian) * 0.5 : 0
|
||||
|
||||
// 计算高亮效果的放大比例(未高亮,则比例为 1)
|
||||
let hoverRate = isHovered ? 1.1 : 1
|
||||
// 返回曲面参数方程
|
||||
return {
|
||||
u: {
|
||||
min: -Math.PI,
|
||||
max: Math.PI * 3,
|
||||
step: Math.PI / 32
|
||||
},
|
||||
v: {
|
||||
min: 0,
|
||||
max: Math.PI * 2,
|
||||
step: Math.PI / 20
|
||||
},
|
||||
x: function (u: any, v: any) {
|
||||
if (u < startRadian) {
|
||||
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
|
||||
}
|
||||
if (u > endRadian) {
|
||||
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
|
||||
}
|
||||
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
|
||||
},
|
||||
y: function (u: any, v: any) {
|
||||
if (u < startRadian) {
|
||||
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
|
||||
}
|
||||
if (u > endRadian) {
|
||||
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
|
||||
}
|
||||
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
|
||||
},
|
||||
z: function (u: any, v: any) {
|
||||
if (u < -Math.PI * 0.5) {
|
||||
return Math.sin(u)
|
||||
}
|
||||
if (u > Math.PI * 2.5) {
|
||||
return Math.sin(u) * h * 0.1
|
||||
}
|
||||
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1
|
||||
}
|
||||
}
|
||||
}
|
||||
// 配置时
|
||||
watch(
|
||||
() => props.chartConfig.option.dataset,
|
||||
newData => {
|
||||
try {
|
||||
if (!isPreview()) {
|
||||
dataHandle(newData)
|
||||
} else {
|
||||
// option.value = props.chartConfig.option
|
||||
// dataHandle(newData)
|
||||
dataHandle(newData)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.grid3D.viewControl.alpha,
|
||||
newData => {
|
||||
props.chartConfig.option.grid3D.viewControl.alpha = newData
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.grid3D.viewControl.distance,
|
||||
newData => {
|
||||
props.chartConfig.option.grid3D.viewControl.distance = newData
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.grid3D.boxHeight,
|
||||
newData => {
|
||||
props.chartConfig.option.grid3D.boxHeight = newData
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.legend.orient,
|
||||
newData => {
|
||||
props.chartConfig.option.orient = newData
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.showLableLineImage,
|
||||
newData => {
|
||||
props.chartConfig.option.showLableLineImage = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
|
||||
props.chartConfig.option.series[3].label.formatter = newData
|
||||
? props.chartConfig.option.labelStyleType === '样式一'
|
||||
? ' {c|{c}个}\n\n {img|}\n\n {a| 占比{d}%}'
|
||||
: props.chartConfig.option.labelStyleType === '样式二'
|
||||
? ' {style|{c} 个}\n\n {img|}\n\n {e|{b}}'
|
||||
: ' {style|{d}%}\n\n {img|}\n\n {f|{b}}'
|
||||
: props.chartConfig.option.labelStyleType === '样式一'
|
||||
? ' {c|{c}个}\n\n {dot|}\n\n {a| 占比{d}%}'
|
||||
: props.chartConfig.option.labelStyleType === '样式二'
|
||||
? ' {style|{c} 个}\n\n {dot|}\n\n{e|{b}}'
|
||||
: ' {style|{d}%}\n\n {dot|}\n\n {f|{b}}'
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelLineColor,
|
||||
newData => {
|
||||
props.chartConfig.option.labelLineColor = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
|
||||
props.chartConfig.option.series[3].labelLine.lineStyle.color = newData
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelPadding1,
|
||||
newData => {
|
||||
props.chartConfig.option.labelPadding1 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label && newData) {
|
||||
props.chartConfig.option.series[3].label.rich.c.padding = newData.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
})
|
||||
props.chartConfig.option.series[3].label.rich.style.padding = newData.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelPadding2,
|
||||
newData => {
|
||||
props.chartConfig.option.labelPadding2 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label && newData) {
|
||||
props.chartConfig.option.series[3].label.rich.a.padding = newData.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
})
|
||||
props.chartConfig.option.series[3].label.rich.e.padding = newData.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
})
|
||||
props.chartConfig.option.series[3].label.rich.f.padding = newData.split(',').map(function (str: any) {
|
||||
return parseInt(str)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelColor1,
|
||||
newData => {
|
||||
props.chartConfig.option.labelColor1 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
|
||||
props.chartConfig.option.series[3].label.rich = {
|
||||
dot: {
|
||||
backgroundColor: '#fff', // 适应颜色
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderRadius: 5, // 保证是圆角
|
||||
fontSize: 16,
|
||||
padding: [5, -5, 5, -5] // 这里就保证是要给宽高6的正方形
|
||||
},
|
||||
img: {
|
||||
backgroundColor: { image: '/src/assets/images/chart/charts/dot.png' }, // 适应颜色
|
||||
width: 24,
|
||||
height: 24,
|
||||
// borderRadius: 5, // 保证是圆角
|
||||
// fontSize: 16,
|
||||
padding: [0, -30, 0, -15]
|
||||
},
|
||||
a: {
|
||||
fontSize: 14,
|
||||
color: props.chartConfig.option.labelColor2 || '#fff',
|
||||
align: 'left',
|
||||
padding: [0, 3, 0, -5]
|
||||
},
|
||||
c: {
|
||||
fontSize: 20,
|
||||
color: newData || '#41a6fc',
|
||||
align: 'left',
|
||||
padding: [0, 3, -10, 5]
|
||||
},
|
||||
style: {
|
||||
fontSize: 20,
|
||||
color: newData || '#b8c276',
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// { offset: 0, color: '#fefefe' }, // 开始颜色
|
||||
// { offset: 1, color: '#e0e98d' } // 结束颜色
|
||||
// ]),
|
||||
align: 'left',
|
||||
padding: [0, 3, -10, 5]
|
||||
},
|
||||
e: {
|
||||
fontSize: 14,
|
||||
color: props.chartConfig.option.labelColor2 || '#fff',
|
||||
align: 'left',
|
||||
padding: [0, 0, 0, 23]
|
||||
},
|
||||
f: {
|
||||
fontSize: 14,
|
||||
color: props.chartConfig.option.labelColor2 || '#fff',
|
||||
align: 'left',
|
||||
padding: [0, 0, 0, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelColor2,
|
||||
newData => {
|
||||
props.chartConfig.option.labelColor2 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.lableLineLength1,
|
||||
newData => {
|
||||
props.chartConfig.option.lableLineLength1 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
|
||||
props.chartConfig.option.series[3].labelLine.length = newData
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.lableLineLength2,
|
||||
newData => {
|
||||
props.chartConfig.option.lableLineLength2 = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].labelLine) {
|
||||
props.chartConfig.option.series[3].labelLine.length2 = newData
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.labelStyleType,
|
||||
newData => {
|
||||
props.chartConfig.option.labelStyleType = newData
|
||||
if (props.chartConfig.option.series[3] && props.chartConfig.option.series[3].label) {
|
||||
props.chartConfig.option.series[3].label.formatter = props.chartConfig.option.showLableLineImage
|
||||
? newData === '样式一'
|
||||
? ' {c|{c}个}\n\n {img|}\n\n {a| 占比{d}%}'
|
||||
: newData === '样式二'
|
||||
? ' {style|{c} 个}\n\n {img|}\n\n {e|{b}}'
|
||||
: ' {style|{d}%}\n\n {img|}\n\n {f|{b}}'
|
||||
: newData === '样式一'
|
||||
? ' {c|{c}个}\n\n {dot|}\n\n {a| 占比{d}%}'
|
||||
: newData === '样式二'
|
||||
? ' {style|{c} 个}\n\n {dot|}\n\n{e|{b}}'
|
||||
: ' {style|{d}%}\n\n {dot|}\n\n {f|{b}}'
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.showDataLabels,
|
||||
newData => {
|
||||
if (props.chartConfig.option.series.length > 1) {
|
||||
let lastSeries = props.chartConfig.option.series[props.chartConfig.option.series.length - 1]
|
||||
if (newData) {
|
||||
lastSeries.label.opacity = 1
|
||||
lastSeries.labelLine.show = true
|
||||
} else {
|
||||
lastSeries.label.opacity = 0
|
||||
lastSeries.labelLine.show = false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.showLegendValue,
|
||||
newData => {
|
||||
if (newData) {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
if (props.chartConfig.option.showLegendPercent) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name + ' ' + value
|
||||
// }
|
||||
// return name;
|
||||
}
|
||||
} else {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
if (props.chartConfig.option.showLegendPercent) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartConfig.option.showLegendPercent,
|
||||
newData => {
|
||||
if (props.chartConfig.option.showLegendValue) {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
if (newData) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name + ' ' + value
|
||||
}
|
||||
} else {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
if (newData) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: false
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
dataHandle(props.chartConfig.option.dataset)
|
||||
if (showLegendValue?.value) {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
if (showLegendPercent?.value) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + value + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name + ' ' + value
|
||||
// }
|
||||
// return name;
|
||||
}
|
||||
} else {
|
||||
props.chartConfig.option.legend.formatter = (name: any) => {
|
||||
if (showLegendPercent?.value) {
|
||||
// 计算所有值的总和
|
||||
var total = 0
|
||||
for (var i = 0; i < props.chartConfig.option.dataset.length; i++) {
|
||||
total += props.chartConfig.option.dataset[i].value
|
||||
}
|
||||
// 找到与当前名称对应的值
|
||||
var value = props.chartConfig.option.dataset.find((item: any) => item.name === name).value
|
||||
// 返回格式化的字符串,包括名称和百分比
|
||||
return name + ' ' + ((value / total) * 100).toFixed(1) + '%'
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleHighlight = (params: any) => {
|
||||
if (params.seriesName !== 'mouseoutSeries') {
|
||||
let series = vChartRef.value?.getOption().series
|
||||
series[params.seriesIndex].parametricEquation = getParametricEquation(
|
||||
series[params.seriesIndex].pieData.startRatio,
|
||||
series[params.seriesIndex].pieData.endRatio,
|
||||
false,
|
||||
true, // 设置为高亮状态
|
||||
series[params.seriesIndex].pieStatus.k,
|
||||
series[params.seriesIndex].pieData.value * 1.05 // 高亮时的高度
|
||||
)
|
||||
vChartRef.value?.setOption({ series: series })
|
||||
}
|
||||
}
|
||||
|
||||
const cancelHighlight = (params: any) => {
|
||||
let series = vChartRef.value?.getOption().series
|
||||
series[params.seriesIndex].parametricEquation = getParametricEquation(
|
||||
series[params.seriesIndex].pieData.startRatio,
|
||||
series[params.seriesIndex].pieData.endRatio,
|
||||
false,
|
||||
false, // 取消高亮状态
|
||||
series[params.seriesIndex].pieStatus.k,
|
||||
series[params.seriesIndex].pieData.value // 恢复基础高度
|
||||
)
|
||||
vChartRef.value?.setOption({ series: series })
|
||||
}
|
||||
|
||||
// 预览更新
|
||||
const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
|
||||
props.chartConfig.option.dataset = newData
|
||||
dataHandle(newData)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -2,5 +2,6 @@ import { PieCommonConfig } from './PieCommon/index'
|
||||
import { PieCircleConfig } from './PieCircle/index'
|
||||
import { PieThreeConfig } from './PieThree'
|
||||
import { PieMultConfig } from './PieMult/index'
|
||||
import {PieConfig} from './Pie/index'
|
||||
|
||||
export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig]
|
||||
export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig, PieConfig]
|
||||
|
9
src/packages/components/Charts/index.d.ts
vendored
9
src/packages/components/Charts/index.d.ts
vendored
@ -6,8 +6,9 @@ export enum ChatCategoryEnum {
|
||||
SCATTER = 'Scatters',
|
||||
MAP = 'Maps',
|
||||
MyComponets='MyComponets',
|
||||
ConfinedSpace='ConfinedSpace',
|
||||
MORE = 'Mores'
|
||||
MORE = 'Mores',
|
||||
CONFINE = 'ConfinedSpace',
|
||||
HazardousChemicalsSpace = 'HazardousChemicalsSpace'
|
||||
}
|
||||
|
||||
export enum ChatCategoryEnumName {
|
||||
@ -19,5 +20,7 @@ export enum ChatCategoryEnumName {
|
||||
MyComponets='我的混合组件',
|
||||
ConfinedSpace='有限空间',
|
||||
COMBINATION = '组合图',
|
||||
MORE = '更多'
|
||||
MORE = '更多',
|
||||
CONFINE = '有限空间组件',
|
||||
HazardousChemicalsSpace = '危化品场景'
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ import Mores from './Mores'
|
||||
import Maps from './Maps'
|
||||
import MyComponets from './MyComponents'
|
||||
import ConfinedSpace from './ConfinedSpace'
|
||||
import HazardousChemicalsSpace from './HazardousChemicalsSpace'
|
||||
|
||||
|
||||
export const ChartList = [...Bars, ...Lines, ...Pies, ...Scatters, ...Maps, ...Mores, ...MyComponets
|
||||
, ...ConfinedSpace
|
||||
]
|
||||
export const ChartList = [...Bars, ...Lines, ...Pies, ...Scatters, ...Maps, ...Mores, ...MyComponets, ...ConfinedSpace, ...HazardousChemicalsSpace]
|
||||
|
Loading…
Reference in New Issue
Block a user