This commit is contained in:
Free-sss 2025-08-27 10:07:40 +08:00
commit 713715028b
69 changed files with 7569 additions and 9 deletions

View 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)
}

View 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>

View File

@ -0,0 +1,9 @@
{
"dimensions": ["场所分布情况", "当日作业分布情况"],
"source": [
{
"场所分布情况": 150,
"当日作业分布情况": 75
}
]
}

View 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'
}

View 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
// dimensionsYdimensions.length011X
const oldDimensions = Array.isArray(oldData?.dimensions)&&oldData.dimensions.length >= 1 ? oldData.dimensions.length : 1;
const newDimensions = newData.dimensions.length >= 1 ? newData.dimensions.length : 1;
const dimensionsGap = newDimensions - oldDimensions;
if (dimensionsGap < 0) {
props.chartConfig.option.series.splice(newDimensions - 1)
} else if (dimensionsGap > 0) {
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>

View File

@ -7,6 +7,7 @@ import { BarRankConfig } from './BarRank'
import { BarRank2Config } from './BarRank2' import { BarRank2Config } from './BarRank2'
import { BarRank3Config } from './BarRank3' import { BarRank3Config } from './BarRank3'
import { BarLinearConfig } from './BarLinear/index' import { BarLinearConfig } from './BarLinear/index'
import { Bar3DConfig } from './Bar3d/index'
export default [ export default [
BarCommonConfig, BarCommonConfig,
@ -17,5 +18,6 @@ export default [
BarRankConfig, BarRankConfig,
BarRank2Config, BarRank2Config,
BarRank3Config, BarRank3Config,
BarLinearConfig BarLinearConfig,
Bar3DConfig
] ]

View File

@ -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)
}

View File

@ -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>

View File

@ -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
}
]
}

View File

@ -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

View File

@ -1,4 +1,6 @@
import {MapConfig} from './Map' import {MapConfig} from './Map'
import {PieCircleCommenConfig} from './PieCircleCommen' import {PieCircleCommenConfig} from './PieCircleCommen'
import {AlarmNowListConfig} from './AlarmNowList' import {AlarmNowListConfig} from './AlarmNowList'
export default [MapConfig,PieCircleCommenConfig,AlarmNowListConfig] import { LineDropdownConfig } from './LineDropdown/index'
export default [MapConfig,LineDropdownConfig,PieCircleCommenConfig,AlarmNowListConfig]

View File

@ -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
}

View File

@ -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>

View File

@ -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": "未解决"
}
]
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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>

View File

@ -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
}
]
}

View File

@ -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'
}

View File

@ -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>

View File

@ -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

View File

@ -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 }
}

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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)
}

View File

@ -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>

View File

@ -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

View File

@ -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)
}

View File

@ -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>

View File

@ -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
}
]
}

View File

@ -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'
}

View File

@ -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>

View File

@ -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

View File

@ -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)
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -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 }
]
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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)
}

View File

@ -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>

View File

@ -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'
}

View File

@ -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.bodyres.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)
// }
// nat8093
// https443
// 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>

View 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)
}

View 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>

View 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
}
]

View 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'
}

View 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使labelLine3dsetOption
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/2626px
// legendDataseries
// @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>

View File

@ -2,5 +2,6 @@ import { PieCommonConfig } from './PieCommon/index'
import { PieCircleConfig } from './PieCircle/index' import { PieCircleConfig } from './PieCircle/index'
import { PieThreeConfig } from './PieThree' import { PieThreeConfig } from './PieThree'
import { PieMultConfig } from './PieMult/index' import { PieMultConfig } from './PieMult/index'
import {PieConfig} from './Pie/index'
export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig] export default [PieCommonConfig, PieCircleConfig, PieThreeConfig, PieMultConfig, PieConfig]

View File

@ -6,8 +6,9 @@ export enum ChatCategoryEnum {
SCATTER = 'Scatters', SCATTER = 'Scatters',
MAP = 'Maps', MAP = 'Maps',
MyComponets='MyComponets', MyComponets='MyComponets',
ConfinedSpace='ConfinedSpace', MORE = 'Mores',
MORE = 'Mores' CONFINE = 'ConfinedSpace',
HazardousChemicalsSpace = 'HazardousChemicalsSpace'
} }
export enum ChatCategoryEnumName { export enum ChatCategoryEnumName {
@ -19,5 +20,7 @@ export enum ChatCategoryEnumName {
MyComponets='我的混合组件', MyComponets='我的混合组件',
ConfinedSpace='有限空间', ConfinedSpace='有限空间',
COMBINATION = '组合图', COMBINATION = '组合图',
MORE = '更多' MORE = '更多',
CONFINE = '有限空间组件',
HazardousChemicalsSpace = '危化品场景'
} }

View File

@ -6,8 +6,7 @@ import Mores from './Mores'
import Maps from './Maps' import Maps from './Maps'
import MyComponets from './MyComponents' import MyComponets from './MyComponents'
import ConfinedSpace from './ConfinedSpace' import ConfinedSpace from './ConfinedSpace'
import HazardousChemicalsSpace from './HazardousChemicalsSpace'
export const ChartList = [...Bars, ...Lines, ...Pies, ...Scatters, ...Maps, ...Mores, ...MyComponets export const ChartList = [...Bars, ...Lines, ...Pies, ...Scatters, ...Maps, ...Mores, ...MyComponets, ...ConfinedSpace, ...HazardousChemicalsSpace]
, ...ConfinedSpace
]