feat(危化品场景): 新增危化品场景组件及配置

新增危化品场景相关组件包括折线图、饼图、报警列表、摄像头等,并添加对应的配置文件和样式。扩展图表分类枚举和导出列表,优化现有组件命名规范。

主要变更:
- 新增LineGraph01Haz、PieCenterHaz等组件
- 添加配置文件和示例数据
- 扩展图表分类枚举
- 优化组件命名规范
- 新增SmallBorder通用边框组件
This commit is contained in:
gaohaifeng 2025-08-26 15:43:53 +08:00
parent 1bf9ffb78b
commit f32a08ea99
52 changed files with 5396 additions and 6 deletions

View File

@ -2,9 +2,9 @@ import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/ind
// import { ChatCategoryEnum, ChatCategoryEnumName } from '../..'
export const LineDropdownConfig: ConfigType = {
key: 'LineDropdown',
chartKey: 'VLineDropdown',
conKey: 'VCLineDropdown',
key: 'LineDropdown_ConfinedSpace',
chartKey: 'VLineDropdown_ConfinedSpace',
conKey: 'VCLineDropdown_ConfinedSpace',
title: '下拉折线图',
category: 'ConfinedSpace',
categoryName: '有限空间组件',

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

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

View File

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