Commit 2dd02e8806ad55bc455bcda4633446263b20c28f

Authored by xp.Huang
2 parents ab77670d 3259190b

Merge branch 'perf/chart-mulitple-attr' into 'main_dev'

perf: 优化折线图柱状图属性可以进行多选

See merge request yunteng/thingskit-scada!213
@@ -92,7 +92,8 @@ export interface NodeDataDataSourceJsonType { @@ -92,7 +92,8 @@ export interface NodeDataDataSourceJsonType {
92 deviceId: string 92 deviceId: string
93 deviceProfileId: string 93 deviceProfileId: string
94 deviceProfileTemplateId?: string 94 deviceProfileTemplateId?: string
95 - attr: string 95 + attr: string | string[]
  96 + deviceName?: string
96 97
97 enable: boolean 98 enable: boolean
98 chartOption?: ChartOptionType 99 chartOption?: ChartOptionType
@@ -40,11 +40,10 @@ const contentDataStore = useContentDataStoreWithOut() @@ -40,11 +40,10 @@ const contentDataStore = useContentDataStoreWithOut()
40 export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { 40 export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => {
41 const { getNodeData, getCellInfo, getDeviceInfo } = usePublicFormContext() 41 const { getNodeData, getCellInfo, getDeviceInfo } = usePublicFormContext()
42 const { dataSourceJson } = unref(getNodeData) || {} 42 const { dataSourceJson } = unref(getNodeData) || {}
43 - const { deviceProfileId, deviceId } = dataSourceJson || {} 43 + const { deviceProfileId } = dataSourceJson || {}
44 // transportType:判断是什么类型的设备 code:设备地址码 deviceType:设备类型 44 // transportType:判断是什么类型的设备 code:设备地址码 deviceType:设备类型
45 - let codeType: string | null = ''  
46 - const { transportType, deviceType, codeType: deviceCodeType } = unref(getDeviceInfo) || {}  
47 - codeType = deviceCodeType || (deviceId ? contentDataStore.diveceDetailMap?.[deviceId]?.codeType : null) 45 + const { transportType, deviceType, codeType } = unref(getDeviceInfo) || {}
  46 +
48 const isTemplate = contentDataStore.isTemplate // 判断是否是模板 47 const isTemplate = contentDataStore.isTemplate // 判断是否是模板
49 48
50 return [ 49 return [
@@ -63,6 +63,7 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { @@ -63,6 +63,7 @@ export const formSchemas = (componentKey?: string): FormSchema[] => {
63 onSelect(value: string, option: DeviceItemType) { 63 onSelect(value: string, option: DeviceItemType) {
64 formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null 64 formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null
65 formModel[ContentDataFieldsEnum.ATTR] = null 65 formModel[ContentDataFieldsEnum.ATTR] = null
  66 + formModel[ContentDataFieldsEnum.DEVICE_NAME] = value ? option.alias || option.name : ''
66 }, 67 },
67 filterOption: (inputValue: string, option: DeviceItemType) => { 68 filterOption: (inputValue: string, option: DeviceItemType) => {
68 return option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue) 69 return option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue)
@@ -71,6 +72,12 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { @@ -71,6 +72,12 @@ export const formSchemas = (componentKey?: string): FormSchema[] => {
71 }, 72 },
72 }, 73 },
73 { 74 {
  75 + field: ContentDataFieldsEnum.DEVICE_NAME,
  76 + label: ContentDataFieldsNameEnum.deviceName,
  77 + component: ComponentEnum.INPUT,
  78 + ifShow: false,
  79 + },
  80 + {
74 field: ContentDataFieldsEnum.ATTR, 81 field: ContentDataFieldsEnum.ATTR,
75 label: ContentDataFieldsNameEnum.ATTR, 82 label: ContentDataFieldsNameEnum.ATTR,
76 component: ComponentEnum.API_SELECT, 83 component: ComponentEnum.API_SELECT,
@@ -6,3 +6,10 @@ export function useLatestMessageValue(message: SubscriptionData, attr: string) { @@ -6,3 +6,10 @@ export function useLatestMessageValue(message: SubscriptionData, attr: string) {
6 const [ts, latestValue = null] = lateset || [] 6 const [ts, latestValue = null] = lateset || []
7 return { ts, latestValue } 7 return { ts, latestValue }
8 } 8 }
  9 +
  10 +export function useLatestMultipleMessageValue(message: SubscriptionData, attr: string[], Fn: (attribute: string, value: any, timespan: number) => void) {
  11 + attr.forEach((item) => {
  12 + const [ts = null, latestValue = null] = message[item][0] || []
  13 + Fn(item, ts, latestValue)
  14 + })
  15 +}
@@ -34,13 +34,11 @@ function initChartInstance() { @@ -34,13 +34,11 @@ function initChartInstance() {
34 const { onMessage } = useOnMessage({ 34 const { onMessage } = useOnMessage({
35 onReceiveDataSourceMessage(commandSource, message) { 35 onReceiveDataSourceMessage(commandSource, message) {
36 const { data } = commandSource 36 const { data } = commandSource
37 - const { deviceInfo, attrInfo } = data || {}  
38 - const { deviceName } = deviceInfo || {}  
39 const { attr } = data as NodeDataDataSourceJsonType 37 const { attr } = data as NodeDataDataSourceJsonType
40 const { latestValue } = useLatestMessageValue(message.data, attr) 38 const { latestValue } = useLatestMessageValue(message.data, attr)
41 unref(chartInstance)?.setOption({ 39 unref(chartInstance)?.setOption({
42 title: { 40 title: {
43 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 41 + // text: `${deviceName || ''}-${attrInfo.name || ''}`,
44 }, 42 },
45 series: [{ 43 series: [{
46 data: getSetValue(Number(latestValue)), 44 data: getSetValue(Number(latestValue)),
@@ -3,7 +3,8 @@ import { Button, Divider } from 'ant-design-vue' @@ -3,7 +3,8 @@ import { Button, Divider } from 'ant-design-vue'
3 import { nextTick, onMounted, ref, unref } from 'vue' 3 import { nextTick, onMounted, ref, unref } from 'vue'
4 import { formSchemas } from '../config' 4 import { formSchemas } from '../config'
5 import { ChartComponentEnum } from '..' 5 import { ChartComponentEnum } from '..'
6 -import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' 6 +import { DataSourceForm } from '../component/index'
  7 +// import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm'
7 8
8 import { BasicForm, useForm } from '@/components/Form' 9 import { BasicForm, useForm } from '@/components/Form'
9 import { FormLayoutEnum } from '@/components/Form/src/enum' 10 import { FormLayoutEnum } from '@/components/Form/src/enum'
@@ -46,7 +47,7 @@ const handleSubmit = async () => { @@ -46,7 +47,7 @@ const handleSubmit = async () => {
46 if (contentDataStore.getIsTemplate) 47 if (contentDataStore.getIsTemplate)
47 dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } 48 dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null }
48 49
49 - await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, chartOption: { ...values } } }) 50 + await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, attr: typeof value.attr == 'string' ? [value.attr] : value.attr, chartOption: { ...values } } })
50 savePageContent() 51 savePageContent()
51 createMessage.success('操作成功~') 52 createMessage.success('操作成功~')
52 } 53 }
@@ -57,9 +58,9 @@ const handleSubmit = async () => { @@ -57,9 +58,9 @@ const handleSubmit = async () => {
57 58
58 const handleSetFormValues = async () => { 59 const handleSetFormValues = async () => {
59 const { dataSourceJson } = unref(getNodeData) || {} 60 const { dataSourceJson } = unref(getNodeData) || {}
60 - const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId } = dataSourceJson || {} 61 + const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, deviceName } = dataSourceJson || {}
61 await nextTick() 62 await nextTick()
62 - unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceProfileTemplateId }) 63 + unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceName, deviceProfileId, deviceProfileTemplateId })
63 setFieldsValue({ ...chartOption }) 64 setFieldsValue({ ...chartOption })
64 } 65 }
65 66
1 import { isLightboxMode } from '@/utils/env' 1 import { isLightboxMode } from '@/utils/env'
2 2
3 -const data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']  
4 -const seriesList = [  
5 - { value: 120, name: '123', itemStyle: { color: '#5470c6' } },  
6 - { value: 150, name: '456', itemStyle: { color: '#5470c6' } },  
7 - { value: 40, name: '789', itemStyle: { color: '#5470c6' } },  
8 - { value: 139, name: '111', itemStyle: { color: '#5470c6' } },  
9 - { value: 130, name: '222', itemStyle: { color: '#5470c6' } },  
10 - { value: 125, name: '333', itemStyle: { color: '#5470c6' } },  
11 - { value: 110, name: '444', itemStyle: { color: '#5470c6' } },  
12 -]  
13 export const defaultOption = { 3 export const defaultOption = {
14 - color: ['#3398DB'],  
15 tooltip: { 4 tooltip: {
16 - // 提示框  
17 - trigger: 'item',  
18 - confine: true,  
19 - axisPointer: {  
20 - type: 'shadow',  
21 - },  
22 }, 5 },
23 - xAxis: {  
24 - type: 'category',  
25 - data: isLightboxMode() ? [] : data,  
26 - axisTick: {  
27 - alignWithLabel: true, 6 + calculable: true,
  7 + xAxis: [
  8 + {
  9 + type: 'category',
  10 + // prettier-ignore
  11 + data: isLightboxMode() ? [] : ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
28 }, 12 },
  13 + ],
  14 + legend: {
  15 + top: '8%',
  16 + left: 'center',
  17 + data: [''],
29 }, 18 },
  19 +
30 grid: { 20 grid: {
31 - top: '10%',  
32 - left: '10%',  
33 - bottom: '10%',  
34 - },  
35 - yAxis: {  
36 - type: 'value', 21 + top: '25%',
  22 + left: '6%',
  23 + right: '10%',
  24 + bottom: '8%',
  25 + containLabel: true,
37 }, 26 },
  27 + yAxis: [
  28 + {
  29 + type: 'value',
  30 + },
  31 + ],
38 series: [ 32 series: [
39 { 33 {
40 - name: '', 34 + name: 'Rainfall',
  35 + type: 'bar',
  36 + data: [
  37 + 25.6, 76.7, 42.0, 27.0, 23.2,
  38 + ],
  39 + },
  40 + {
  41 + name: 'Evaporation',
41 type: 'bar', 42 type: 'bar',
42 - barWidth: '60%',  
43 - data: isLightboxMode () ? [] : seriesList, 43 + data: isLightboxMode()
  44 + ? []
  45 + : [
  46 + 28.7, 32.6, 70.7, 29.0, 26.4,
  47 + ],
44 }, 48 },
45 ], 49 ],
46 } 50 }
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 -import { computed, onMounted, onUnmounted, ref, unref } from 'vue' 2 +import { computed, onMounted, onUnmounted, ref, toRaw, unref } from 'vue'
3 import { init } from 'echarts' 3 import { init } from 'echarts'
4 import type { ECharts, EChartsOption } from 'echarts' 4 import type { ECharts, EChartsOption } from 'echarts'
5 import { defaultOption } from './index.config' 5 import { defaultOption } from './index.config'
@@ -10,6 +10,7 @@ import { SocketSubscriberEnum } from '@/enums/datasource' @@ -10,6 +10,7 @@ import { SocketSubscriberEnum } from '@/enums/datasource'
10 import type { SubscriptionData } from '@/core/websocket/type/message' 10 import type { SubscriptionData } from '@/core/websocket/type/message'
11 import { formatToDateTime } from '@/utils/dateUtil' 11 import { formatToDateTime } from '@/utils/dateUtil'
12 import type { CommandSource } from '@/core/websocket/processor' 12 import type { CommandSource } from '@/core/websocket/processor'
  13 +import { useProductsStore } from '@/store/modules/products'
13 14
14 const props = defineProps<{ 15 const props = defineProps<{
15 config: CreateComponentType 16 config: CreateComponentType
@@ -21,40 +22,80 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() @@ -21,40 +22,80 @@ const chartElRef = ref<Nullable<HTMLDivElement>>()
21 22
22 const chartInstance = ref<Nullable<ECharts>>() 23 const chartInstance = ref<Nullable<ECharts>>()
23 24
  25 +const seriesData = ref<any>([])
  26 +const XAxisData = ref<any>([])
  27 +const titleATTR = ref<string[] | any>([])
  28 +
24 function initChartInstance() { 29 function initChartInstance() {
25 chartInstance.value = init(unref(chartElRef)) 30 chartInstance.value = init(unref(chartElRef))
26 chartInstance.value.setOption(defaultOption) 31 chartInstance.value.setOption(defaultOption)
27 } 32 }
28 33
  34 +const productsStore = useProductsStore()
  35 +
29 const handleHistoryData = (commandSource: CommandSource, message: SubscriptionData) => { 36 const handleHistoryData = (commandSource: CommandSource, message: SubscriptionData) => {
30 const { data } = commandSource 37 const { data } = commandSource
31 - const { attrInfo, deviceInfo, attr } = data as NodeDataDataSourceJsonType  
32 - const { deviceName } = deviceInfo || {} 38 + const { attr, deviceName } = data as NodeDataDataSourceJsonType as any
33 39
34 - const historyData = message[attr]  
35 - const xAxisData: string[] = []  
36 - const seriesData: number[] = [] 40 + attr.forEach((item: any) => { // 得到所有时间
  41 + XAxisData.value.push(...message[item].map((item1) => {
  42 + const [ts] = item1
  43 + return ts
  44 + }))
  45 + })
  46 + XAxisData.value = Array.from(new Set(XAxisData.value)).sort((a: any, b: any) => a - b)// 去重获取到的时间
  47 + // 初始化有几个柱
  48 + seriesData.value.forEach((item: any) => {
  49 + item.data = unref(XAxisData).map((time: any) => ({ name: time, value: '' }))
  50 + })
37 51
38 - for (const item of historyData) {  
39 - const [ts, value] = item  
40 - xAxisData.push(formatToDateTime(ts))  
41 - seriesData.push(value)  
42 - } 52 + // 跟每个柱添加数据
  53 + seriesData.value.forEach((item: any) => {
  54 + item.data.forEach((item1: any) => {
  55 + message[item.attribute].forEach((messageItem) => {
  56 + const [ts, value] = messageItem
  57 + if (item1.name === ts)
  58 + item1.value = value || ''
  59 + // item1.name = formatToDateTime(item1.name)
  60 + })
  61 + })
  62 + })
43 63
44 unref(chartInstance)?.setOption({ 64 unref(chartInstance)?.setOption({
45 title: { 65 title: {
46 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 66 + text: `${deviceName || ''}`,
  67 + },
  68 + xAxis: [
  69 + {
  70 + type: 'category',
  71 + data: toRaw(unref(XAxisData.value.map((item: string | number) => formatToDateTime(item)))),
  72 + },
  73 + ],
  74 + legend: {
  75 + data: unref(titleATTR),
47 }, 76 },
48 - xAxis: { data: xAxisData },  
49 - series: { data: seriesData }, 77 + series: toRaw(unref(seriesData).map((item: { type: string; name: string; data: any; attribute: string }) => {
  78 + const { type, name, data } = item
  79 + return {
  80 + type,
  81 + name,
  82 + data: data.map((item: any) => item.value),
  83 + }
  84 + })),
50 } as EChartsOption) 85 } as EChartsOption)
51 } 86 }
52 87
53 const { onMessage } = useOnMessage({ 88 const { onMessage } = useOnMessage({
54 onReceiveDataSourceMessage(commandSource, message) { 89 onReceiveDataSourceMessage(commandSource, message) {
55 const { data } = commandSource 90 const { data } = commandSource
56 - const { chartOption } = data as NodeDataDataSourceJsonType 91 + const { chartOption, attr, deviceProfileId } = data as NodeDataDataSourceJsonType
57 const { queryType } = chartOption || {} 92 const { queryType } = chartOption || {}
  93 + if (!seriesData.value.length) {
  94 + (attr as string[]).forEach((item: string) => {
  95 + titleATTR.value.push(productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName)
  96 + seriesData.value.push({ attribute: item, data: [], type: 'bar', name: productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName })
  97 + })
  98 + }
58 99
59 if (queryType === SocketSubscriberEnum.HISTORY_CMDS) 100 if (queryType === SocketSubscriberEnum.HISTORY_CMDS)
60 handleHistoryData(commandSource, message.data) 101 handleHistoryData(commandSource, message.data)
@@ -36,8 +36,6 @@ onMounted(() => { @@ -36,8 +36,6 @@ onMounted(() => {
36 36
37 const onReceiveDataSourceMessage = (commandSource: CommandSource, message: SubscriptionUpdateMsg) => { 37 const onReceiveDataSourceMessage = (commandSource: CommandSource, message: SubscriptionUpdateMsg) => {
38 const { data } = commandSource 38 const { data } = commandSource
39 - const { deviceInfo, attrInfo } = data || {}  
40 - const { deviceName } = deviceInfo || {}  
41 const { attr } = data as NodeDataDataSourceJsonType 39 const { attr } = data as NodeDataDataSourceJsonType
42 const { latestValue } = useLatestMessageValue(message.data, attr) 40 const { latestValue } = useLatestMessageValue(message.data, attr)
43 unref(chartInstance)?.setOption({ 41 unref(chartInstance)?.setOption({
@@ -46,7 +44,7 @@ const onReceiveDataSourceMessage = (commandSource: CommandSource, message: Subsc @@ -46,7 +44,7 @@ const onReceiveDataSourceMessage = (commandSource: CommandSource, message: Subsc
46 44
47 }], 45 }],
48 title: { 46 title: {
49 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 47 + // text: `${deviceName || ''}-${attrInfo.name || ''}`,
50 }, 48 },
51 } as EChartsOption) 49 } as EChartsOption)
52 } 50 }
@@ -3,7 +3,9 @@ import { Button, Divider } from 'ant-design-vue' @@ -3,7 +3,9 @@ import { Button, Divider } from 'ant-design-vue'
3 import { nextTick, onMounted, ref, unref } from 'vue' 3 import { nextTick, onMounted, ref, unref } from 'vue'
4 import { formSchemas } from '../config' 4 import { formSchemas } from '../config'
5 import { ChartComponentEnum } from '..' 5 import { ChartComponentEnum } from '..'
6 -import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' 6 +
  7 +import { DataSourceForm } from '../component/index'
  8 +// import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm'
7 9
8 import { BasicForm, useForm } from '@/components/Form' 10 import { BasicForm, useForm } from '@/components/Form'
9 import { FormLayoutEnum } from '@/components/Form/src/enum' 11 import { FormLayoutEnum } from '@/components/Form/src/enum'
@@ -33,9 +35,9 @@ const dataSourceElRef = ref<Nullable<InstanceType<typeof DataSourceForm>>>() @@ -33,9 +35,9 @@ const dataSourceElRef = ref<Nullable<InstanceType<typeof DataSourceForm>>>()
33 35
34 const handleSetFormValues = async () => { 36 const handleSetFormValues = async () => {
35 const { dataSourceJson } = unref(getNodeData) || {} 37 const { dataSourceJson } = unref(getNodeData) || {}
36 - const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId } = dataSourceJson || {} 38 + const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, deviceName } = dataSourceJson || {}
37 await nextTick() 39 await nextTick()
38 - unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceProfileTemplateId }) 40 + unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceProfileTemplateId, deviceName })
39 setFieldsValue({ ...chartOption }) 41 setFieldsValue({ ...chartOption })
40 } 42 }
41 43
@@ -54,7 +56,7 @@ const handleSubmit = async () => { @@ -54,7 +56,7 @@ const handleSubmit = async () => {
54 if (contentDataStore.getIsTemplate) 56 if (contentDataStore.getIsTemplate)
55 dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } 57 dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null }
56 58
57 - saveNodeAllData({ dataSourceJson: { ...dataSourceJson, chartOption: { ...values } } }) 59 + saveNodeAllData({ dataSourceJson: { ...dataSourceJson, attr: typeof value.attr == 'string' ? [value.attr] : value.attr, chartOption: { ...values } } })
58 createMessage.success('操作成功~') 60 createMessage.success('操作成功~')
59 savePageContent() 61 savePageContent()
60 } 62 }
@@ -3,31 +3,35 @@ import { isLightboxMode } from '@/utils/env' @@ -3,31 +3,35 @@ import { isLightboxMode } from '@/utils/env'
3 3
4 export const getDefaultOption = (): EChartsOption => { 4 export const getDefaultOption = (): EChartsOption => {
5 return { 5 return {
6 - tooltip: {  
7 - trigger: 'item',  
8 - confine: true, 6 + legend: {
  7 + top: '10%',
  8 + left: 'center',
  9 + data: [''],
9 }, 10 },
10 grid: { 11 grid: {
11 - left: '3%',  
12 - right: '4%',  
13 - bottom: '3%', 12 + top: '30%',
  13 + left: '6%',
  14 + right: '10%',
  15 + bottom: '8%',
14 containLabel: true, 16 containLabel: true,
15 }, 17 },
16 - dataset: {  
17 - source: isLightboxMode() 18 + xAxis: {
  19 + type: 'category',
  20 + },
  21 + yAxis: {
  22 + type: 'value',
  23 + boundaryGap: [0, '100%'],
  24 + },
  25 + series: [{
  26 + type: 'line',
  27 + name: '温度',
  28 + data: isLightboxMode()
18 ? [] 29 ? []
19 : [ 30 : [
20 - ['product', '2015', '2016', '2017'],  
21 - ['Matcha Latte', 43.3, 85.8, 93.7],  
22 - ['Milk Tea', 83.1, 73.4, 55.1],  
23 - ['Cheese Cocoa', 86.4, 65.2, 82.5],  
24 - ['Walnut Brownie', 72.4, 53.9, 39.1],  
25 - ],  
26 - },  
27 - xAxis: { type: 'category' },  
28 - yAxis: {},  
29 - series: [  
30 - { type: 'line' },  
31 - ], 31 + ['Matcha Latte', 43.3],
  32 + ['Milk Tea', 83.1],
  33 + ['Cheese Cocoa', 86.4],
  34 + ['Walnut Brownie', 72.4]],
  35 + }],
32 } 36 }
33 } 37 }
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 -import { computed, onMounted, onUnmounted, ref, unref } from 'vue'  
3 -import type { DatasetComponentOption, ECharts, EChartsOption } from 'echarts' 2 +import { computed, onMounted, onUnmounted, ref, toRaw, unref } from 'vue'
  3 +import type { ECharts, EChartsOption } from 'echarts'
4 import { init } from 'echarts' 4 import { init } from 'echarts'
5 import { getDefaultOption } from './index.config' 5 import { getDefaultOption } from './index.config'
6 import type { CreateComponentType, RenderComponentExposeType } from '@/core/Library/types' 6 import type { CreateComponentType, RenderComponentExposeType } from '@/core/Library/types'
@@ -9,8 +9,12 @@ import type { NodeDataDataSourceJsonType } from '@/api/node/model' @@ -9,8 +9,12 @@ import type { NodeDataDataSourceJsonType } from '@/api/node/model'
9 import type { SubscriptionData } from '@/core/websocket/type/message' 9 import type { SubscriptionData } from '@/core/websocket/type/message'
10 import { SocketSubscriberEnum } from '@/enums/datasource' 10 import { SocketSubscriberEnum } from '@/enums/datasource'
11 import { formatToDateTime } from '@/utils/dateUtil' 11 import { formatToDateTime } from '@/utils/dateUtil'
12 -import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue'  
13 import type { CommandSource } from '@/core/websocket/processor' 12 import type { CommandSource } from '@/core/websocket/processor'
  13 +import { useLatestMultipleMessageValue } from '@/core/Library/hook/useLatestMessageValue'
  14 +import { useProductsStore } from '@/store/modules/products'
  15 +interface IList {
  16 + time: string | number
  17 +}
14 18
15 const props = defineProps<{ 19 const props = defineProps<{
16 config: CreateComponentType 20 config: CreateComponentType
@@ -22,67 +26,126 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() @@ -22,67 +26,126 @@ const chartElRef = ref<Nullable<HTMLDivElement>>()
22 26
23 const chartInstance = ref<Nullable<ECharts>>() 27 const chartInstance = ref<Nullable<ECharts>>()
24 28
  29 +const maxLength = ref<number>(20)
  30 +
  31 +const timeList = ref<number[] | string[] | any>([])// 存储XAxis的值
  32 +const seriesData = ref<any>([])// 存储series的值
  33 +const titleATTR = ref<string[] | any>([])
  34 +
  35 +const productsStore = useProductsStore()
  36 +
25 function initChartInstance() { 37 function initChartInstance() {
26 chartInstance.value = init(unref(chartElRef)) 38 chartInstance.value = init(unref(chartElRef))
27 chartInstance.value.setOption(getDefaultOption()) 39 chartInstance.value.setOption(getDefaultOption())
28 } 40 }
29 41
30 -const handleHistoryData = (message: SubscriptionData, attr: string) => {  
31 - const data = message[attr]  
32 - const xAxisData: string[] = []  
33 - const seriesData: number[] = []  
34 -  
35 - for (const item of data) {  
36 - const [ts, value] = item  
37 - xAxisData.push(formatToDateTime(ts))  
38 - seriesData.push(value)  
39 - } 42 +const handleHistoryData = (commandSource: CommandSource, message: SubscriptionData, attr: string[] | string) => {
  43 + const { data } = commandSource || {}
  44 + const { deviceName } = data as NodeDataDataSourceJsonType
40 45
  46 + (attr as string[]).forEach((item) => {
  47 + timeList.value.push(...message[item].map((item1) => {
  48 + const [ts] = item1
  49 + return ts
  50 + }))
  51 + })
  52 + timeList.value = Array.from(new Set(timeList.value))?.sort((a: any, b: any) => a - b)// 去重获取到的时间
  53 + seriesData.value.forEach((item: any) => {
  54 + item.data = unref(timeList).map((time: any) => ({ name: time, value: null }))
  55 + })
  56 + seriesData.value.forEach((item: any) => {
  57 + item.data.forEach((item1: any) => {
  58 + message[item.attribute].forEach((messageItem) => {
  59 + const [ts, value] = messageItem
  60 + if (item1.name === ts) {
  61 + item1.value = value || undefined
  62 + item1.name = formatToDateTime(item1.name)
  63 + }
  64 + })
  65 + })
  66 + })
41 unref(chartInstance)?.setOption({ 67 unref(chartInstance)?.setOption({
42 - xAxis: { data: xAxisData },  
43 - series: { data: seriesData }, 68 + title: {
  69 + text: `${deviceName || ''}`,
  70 + },
  71 + xAxis: {
  72 + data: toRaw(unref(timeList.value.map((item: string | number) => formatToDateTime(item)))),
  73 + },
  74 + series: toRaw(
  75 + unref(seriesData).map((item: { type: string; name: string; data: any }) => {
  76 + const { type, name, data } = item
  77 + return {
  78 + type,
  79 + name,
  80 + data,
  81 + }
  82 + }),
  83 + ),
44 } as EChartsOption) 84 } as EChartsOption)
45 } 85 }
46 86
47 -function sliceData(data: any[], maxLength = 20) {  
48 - if (data.length > maxLength)  
49 - return data.slice(1)  
50 - return data  
51 -} 87 +// const contentDataStore = useContentDataStoreWithOut()
52 88
53 -const handlerTimeSeriesData = (commandSource: CommandSource, message: SubscriptionData, attr: string) => { 89 +const handlerTimeSeriesData = (commandSource: CommandSource, message: SubscriptionData, attr: string[] | string) => {
54 const { data } = commandSource 90 const { data } = commandSource
55 - const { attrInfo, deviceInfo } = data as NodeDataDataSourceJsonType  
56 - const { deviceName } = deviceInfo || {}  
57 - const { ts, latestValue } = useLatestMessageValue(message, attr)  
58 - const option = unref(chartInstance)?.getOption() as EChartsOption  
59 - const oldDataset = (option.dataset as DatasetComponentOption[])?.[0].source as Recordable[]  
60 - oldDataset.push({  
61 - ts: formatToDateTime(ts),  
62 - [attr]: latestValue, 91 + const { deviceName } = data as NodeDataDataSourceJsonType
  92 + const list: IList | any = {}// 记录时间
  93 + useLatestMultipleMessageValue(message, attr as any, (attribute, timespan, value) => {
  94 + list.time = timespan || list.time
  95 + seriesData.value.forEach((item: any) => {
  96 + if (item.attribute === attribute) {
  97 + item.data.push({
  98 + name: formatToDateTime(list.time),
  99 + value: value || undefined,
  100 + })
  101 + }
  102 + if (item.data.length > unref(maxLength))
  103 + item.data.shift()
  104 + })
63 }) 105 })
  106 + list.time && timeList.value.push(formatToDateTime(list.time))
  107 + if (unref(timeList).length > unref(maxLength))
  108 + timeList.value.shift()
64 109
65 unref(chartInstance)?.setOption({ 110 unref(chartInstance)?.setOption({
66 title: { 111 title: {
67 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 112 + text: `${deviceName || ''}`,
68 }, 113 },
69 - dataset: {  
70 - dimensions: ['ts', attr],  
71 - source: sliceData(oldDataset), 114 + xAxis: {
  115 + data: toRaw(unref(timeList)),
72 }, 116 },
  117 + legend: {
  118 + data: unref(titleATTR),
  119 + } as any,
  120 + series: toRaw(
  121 + unref(seriesData).map((item: { type: string; name: string; data: any }) => {
  122 + const { type, name, data } = item
  123 + return {
  124 + type,
  125 + name,
  126 + data,
  127 + }
  128 + }),
  129 + ),
73 } as EChartsOption) 130 } as EChartsOption)
74 } 131 }
75 132
76 const { onMessage } = useOnMessage({ 133 const { onMessage } = useOnMessage({
77 onReceiveDataSourceMessage(commandSource, message) { 134 onReceiveDataSourceMessage(commandSource, message) {
78 const { data } = commandSource 135 const { data } = commandSource
79 - const { chartOption, attr } = data as NodeDataDataSourceJsonType 136 + const { chartOption, attr, deviceProfileId } = data as NodeDataDataSourceJsonType
80 const { queryType } = chartOption || {} 137 const { queryType } = chartOption || {}
  138 + if (!seriesData.value.length) {
  139 + (attr as string[]).forEach((item: string) => {
  140 + titleATTR.value.push(productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName)
  141 + seriesData.value.push({ attribute: item, data: [], type: 'line', name: productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName })
  142 + })
  143 + }
81 144
82 if (queryType === SocketSubscriberEnum.TS_SUB_CMDS) 145 if (queryType === SocketSubscriberEnum.TS_SUB_CMDS)
83 handlerTimeSeriesData(commandSource, message.data, attr) 146 handlerTimeSeriesData(commandSource, message.data, attr)
84 else if (queryType === SocketSubscriberEnum.HISTORY_CMDS) 147 else if (queryType === SocketSubscriberEnum.HISTORY_CMDS)
85 - handleHistoryData(message.data, attr) 148 + handleHistoryData(commandSource, message.data, attr)
86 }, 149 },
87 }) 150 })
88 151
@@ -34,13 +34,11 @@ function initChartInstance() { @@ -34,13 +34,11 @@ function initChartInstance() {
34 const { onMessage } = useOnMessage({ 34 const { onMessage } = useOnMessage({
35 onReceiveDataSourceMessage(commandSource, message) { 35 onReceiveDataSourceMessage(commandSource, message) {
36 const { data } = commandSource 36 const { data } = commandSource
37 - const { deviceInfo, attrInfo } = data || {}  
38 - const { deviceName } = deviceInfo || {}  
39 const { attr } = data as NodeDataDataSourceJsonType 37 const { attr } = data as NodeDataDataSourceJsonType
40 const { latestValue } = useLatestMessageValue(message.data, attr) 38 const { latestValue } = useLatestMessageValue(message.data, attr)
41 unref(chartInstance)?.setOption({ 39 unref(chartInstance)?.setOption({
42 title: { 40 title: {
43 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 41 + // text: `${deviceName || ''}-${attrInfo.name || ''}`,
44 }, 42 },
45 series: [{ 43 series: [{
46 data: getSetValue(Number(latestValue)), 44 data: getSetValue(Number(latestValue)),
@@ -26,13 +26,11 @@ function initChartInstance() { @@ -26,13 +26,11 @@ function initChartInstance() {
26 const { onMessage } = useOnMessage({ 26 const { onMessage } = useOnMessage({
27 onReceiveDataSourceMessage(commandSource, message) { 27 onReceiveDataSourceMessage(commandSource, message) {
28 const { data } = commandSource 28 const { data } = commandSource
29 - const { deviceInfo, attrInfo } = (data || {}) as NodeDataDataSourceJsonType  
30 - const { deviceName } = deviceInfo || {}  
31 const { attr } = data as NodeDataDataSourceJsonType 29 const { attr } = data as NodeDataDataSourceJsonType
32 const { latestValue } = useLatestMessageValue(message.data, attr) 30 const { latestValue } = useLatestMessageValue(message.data, attr)
33 unref(chartInstance)?.setOption({ 31 unref(chartInstance)?.setOption({
34 title: { 32 title: {
35 - text: `${deviceName || ''}-${attrInfo.name || ''}`, 33 + // text: `${deviceName || ''}-${attrInfo.name || ''}`,
36 }, 34 },
37 series: [{ 35 series: [{
38 data: [{ 36 data: [{
  1 +import { unref } from 'vue'
  2 +import { getDeviceAttributes, getListByConfigurationId, getListByDeviceProfileIds } from '@/api/device'
  3 +import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model'
  4 +import type { FormSchema } from '@/components/Form'
  5 +import { ComponentEnum } from '@/components/Form/src/enum'
  6 +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum, DataTypeEnum } from '@/enums/datasource'
  7 +import { useContentDataStoreWithOut } from '@/store/modules/contentData'
  8 +import type { ProductAndDevice } from '@/api/content/model'
  9 +import { ControlComponentEnum } from '@/core/Library/packages/Control'
  10 +import { useParseParams } from '@/core/LoadData'
  11 +
  12 +const contentDataStore = useContentDataStoreWithOut()
  13 +export const formSchemas = (componentKey?: string): FormSchema[] => {
  14 + const isTemplate = contentDataStore.isTemplate // 判断是否是模板组态
  15 + const isTemplateLink = contentDataStore.getIsTemplateLink
  16 + const params = useParseParams()
  17 + const { configurationId } = params
  18 + return [
  19 + {
  20 + field: ContentDataFieldsEnum.DEVICE_PROFILE_ID,
  21 + label: ContentDataFieldsNameEnum.DEVICE_PROFILE_ID,
  22 + component: ComponentEnum.INPUT,
  23 + ifShow: false,
  24 + },
  25 + {
  26 + field: ContentDataFieldsEnum.DEVICE_PROFILE_TEMPLATE_ID, // 模板产品id
  27 + label: ContentDataFieldsNameEnum.DEVICE_PROFILE_ID,
  28 + component: ComponentEnum.API_SELECT,
  29 + ifShow: !!isTemplate,
  30 + required: !!isTemplate,
  31 + componentProps: ({ formModel }) => {
  32 + return {
  33 + options: (unref(contentDataStore.getProductAndDevice) || []).map((item: ProductAndDevice) => ({ label: item.profileName || item.name, value: item.profileId, transportType: item?.transportType, deviceType: item?.deviceType })),
  34 + placeholder: '请选择产品',
  35 + onSelect(value: string) {
  36 + formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value
  37 + formModel[ContentDataFieldsEnum.ATTR] = []
  38 + },
  39 + getPopupContainer: () => document.body,
  40 + }
  41 + },
  42 + },
  43 + {
  44 + field: ContentDataFieldsEnum.DEVICE_ID,
  45 + label: ContentDataFieldsNameEnum.DEVICE_ID,
  46 + component: ComponentEnum.API_SELECT,
  47 + ifShow: !isTemplate,
  48 + required: !isTemplate,
  49 + componentProps: ({ formModel }) => {
  50 + const organizationId = window.useParseParams().organizationId
  51 + if (!organizationId) return
  52 + return {
  53 + showSearch: true,
  54 + api: async (params: Recordable) => {
  55 + if (isTemplateLink) return await getListByConfigurationId(configurationId!)
  56 + return await getListByDeviceProfileIds(params)
  57 + },
  58 + params: {
  59 + deviceProfileIds: unref(contentDataStore.getProductIds),
  60 + organizationId,
  61 + },
  62 + aliasField: 'alias',
  63 + fieldNames: { label: 'name', value: 'tbDeviceId' },
  64 + onSelect(value: string, option: DeviceItemType) {
  65 + formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null
  66 + formModel[ContentDataFieldsEnum.ATTR] = []
  67 + formModel[ContentDataFieldsEnum.DEVICE_NAME] = value ? option.alias || option.name : ''
  68 + },
  69 + filterOption: (inputValue: string, option: DeviceItemType) => {
  70 + return option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue)
  71 + },
  72 + }
  73 + },
  74 + },
  75 + {
  76 + field: ContentDataFieldsEnum.DEVICE_NAME,
  77 + label: ContentDataFieldsNameEnum.deviceName,
  78 + component: ComponentEnum.INPUT,
  79 + ifShow: false,
  80 + },
  81 + {
  82 + field: ContentDataFieldsEnum.ATTR,
  83 + label: ContentDataFieldsNameEnum.ATTR,
  84 + component: ComponentEnum.API_SELECT,
  85 + required: true,
  86 + componentProps: ({ formModel }) => {
  87 + const deviceProfileId = isTemplate ? formModel[ContentDataFieldsEnum.DEVICE_PROFILE_TEMPLATE_ID] : formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID]
  88 + return {
  89 + showSearch: true,
  90 + mode: 'multiple',
  91 + api: async (params: string) => {
  92 + if (!deviceProfileId) return []
  93 + const options = await getDeviceAttributes(params)
  94 + if (componentKey === ControlComponentEnum.SWITCH) { // 开关只返回bool
  95 + return options.filter((item) => {
  96 + return item.detail.dataType.type === DataTypeEnum.BOOL
  97 + })
  98 + }
  99 + return options
  100 + },
  101 + params: deviceProfileId,
  102 + fieldNames: { label: 'name', value: 'identifier' },
  103 + filterOption: (inputValue: string, option: ThingsModelItemType) => {
  104 + return option.name.includes(inputValue)
  105 + },
  106 + }
  107 + },
  108 + },
  109 + ]
  110 +}
  1 +export { default as DataSourceForm } from './index.vue'
  1 +<script setup lang="ts">
  2 +import { computed, unref } from 'vue'
  3 +import type { RuleError } from 'ant-design-vue/lib/form/interface'
  4 +import { formSchemas } from './config'
  5 +import { FormLayoutEnum } from '@/components/Form/src/enum'
  6 +import { BasicForm, useForm } from '@/components/Form'
  7 +
  8 +export interface ComponentExposeType {
  9 + getFieldsValue: () => any
  10 + setFieldsValue: (value: any) => void
  11 + validate: () => Promise<RuleError | any>
  12 +}
  13 +
  14 +const props = defineProps<{ componentKey?: string }>()
  15 +defineEmits(['fieldValueChange'])
  16 +
  17 +const getComponentKey = computed(() => {
  18 + return props.componentKey
  19 +})
  20 +
  21 +const [registerForm, formActionType] = useForm({
  22 + schemas: formSchemas(unref(getComponentKey)),
  23 + showActionButtonGroup: false,
  24 + layout: FormLayoutEnum.HORIZONTAL,
  25 + labelWidth: 70,
  26 +})
  27 +
  28 +const getFieldsValue = () => {
  29 + return formActionType.getFieldsValue()
  30 +}
  31 +
  32 +const validate = async () => {
  33 + return await formActionType.validate()
  34 +}
  35 +
  36 +const setFieldsValue = (value: Recordable) => {
  37 + formActionType.setFieldsValue(value)
  38 + formActionType.clearValidate()
  39 +}
  40 +
  41 +defineExpose<ComponentExposeType>({
  42 + getFieldsValue,
  43 + setFieldsValue,
  44 + validate,
  45 +})
  46 +</script>
  47 +
  48 +<template>
  49 + <BasicForm @register="registerForm" @field-value-change="$emit('fieldValueChange')" />
  50 +</template>
@@ -238,6 +238,7 @@ export enum ContentDataFieldsEnum { @@ -238,6 +238,7 @@ export enum ContentDataFieldsEnum {
238 DEVICE_ID = 'deviceId', 238 DEVICE_ID = 'deviceId',
239 ORG_ID = 'orgId', 239 ORG_ID = 'orgId',
240 ATTR = 'attr', 240 ATTR = 'attr',
  241 + DEVICE_NAME = 'deviceName',
241 VIDEO_FILTER = 'videoFilter', 242 VIDEO_FILTER = 'videoFilter',
242 VIDEO_ID = 'id', 243 VIDEO_ID = 'id',
243 VIDEO_URL = 'videoUrl', 244 VIDEO_URL = 'videoUrl',
@@ -253,6 +254,7 @@ export enum ContentDataFieldsNameEnum { @@ -253,6 +254,7 @@ export enum ContentDataFieldsNameEnum {
253 ORG_ID = '组织', 254 ORG_ID = '组织',
254 DEVICE_ID = '设备', 255 DEVICE_ID = '设备',
255 ATTR = '属性', 256 ATTR = '属性',
  257 + deviceName = '设备名称',
256 VIDEO_ID = '视频流', 258 VIDEO_ID = '视频流',
257 ACCESS_MODE = 'ACCESS_MODE', 259 ACCESS_MODE = 'ACCESS_MODE',
258 VIDEO_URL = '视频地址', 260 VIDEO_URL = '视频地址',
1 import { defineStore } from 'pinia' 1 import { defineStore } from 'pinia'
2 -import { toRaw, unref } from 'vue' 2 +import { toRaw } from 'vue'
3 import { store } from '..' 3 import { store } from '..'
4 import type { NodeDataType } from '@/api/node/model' 4 import type { NodeDataType } from '@/api/node/model'
5 import type { ProductAndDevice } from '@/api/content/model' 5 import type { ProductAndDevice } from '@/api/content/model'
@@ -7,12 +7,6 @@ import type { CreateComponentType } from '@/core/Library/types' @@ -7,12 +7,6 @@ import type { CreateComponentType } from '@/core/Library/types'
7 import { isShareMode } from '@/utils/env' 7 import { isShareMode } from '@/utils/env'
8 import { ConfigurationAuthEnum, ConfigurationTemplateAuthEnum } from '@/enums/authEnum' 8 import { ConfigurationAuthEnum, ConfigurationTemplateAuthEnum } from '@/enums/authEnum'
9 9
10 -interface DeviceList {  
11 - deviceId: string  
12 - name: string  
13 - codeType: string  
14 -}  
15 -  
16 interface ContentDataStoreType { 10 interface ContentDataStoreType {
17 contentData: NodeDataType[] 11 contentData: NodeDataType[]
18 configurationId: Nullable<string> 12 configurationId: Nullable<string>
@@ -20,7 +14,6 @@ interface ContentDataStoreType { @@ -20,7 +14,6 @@ interface ContentDataStoreType {
20 isTemplate?: number | null | string 14 isTemplate?: number | null | string
21 configurationContentList?: any 15 configurationContentList?: any
22 configurationContentId: Nullable<string> 16 configurationContentId: Nullable<string>
23 - diveceDetailMap: Record<string, DeviceList>  
24 hasDesignAuth?: boolean 17 hasDesignAuth?: boolean
25 hasPerviewAuth?: boolean 18 hasPerviewAuth?: boolean
26 isTemplateLink?: boolean 19 isTemplateLink?: boolean
@@ -35,7 +28,6 @@ export const useContentDataStore = defineStore('app-content-data', { @@ -35,7 +28,6 @@ export const useContentDataStore = defineStore('app-content-data', {
35 productAndDevice: [], 28 productAndDevice: [],
36 configurationContentList: [], 29 configurationContentList: [],
37 configurationContentId: null, 30 configurationContentId: null,
38 - diveceDetailMap: {},  
39 hasDesignAuth: true, 31 hasDesignAuth: true,
40 hasPerviewAuth: true, 32 hasPerviewAuth: true,
41 isTemplateLink: false, 33 isTemplateLink: false,
@@ -70,17 +62,9 @@ export const useContentDataStore = defineStore('app-content-data', { @@ -70,17 +62,9 @@ export const useContentDataStore = defineStore('app-content-data', {
70 62
71 setProductAndDevice(list: ProductAndDevice[]) { 63 setProductAndDevice(list: ProductAndDevice[]) {
72 this.productAndDevice = list 64 this.productAndDevice = list
73 - this.doBuildDeviceMap()  
74 return list 65 return list
75 }, 66 },
76 67
77 - doBuildDeviceMap() {  
78 - const list = this.productAndDevice.map(item => toRaw(unref(item.deviceList))).flat(1)  
79 - this.diveceDetailMap = list.reduce((prev, next) => {  
80 - return { ...prev, [next?.deviceId]: next }  
81 - }, {} as Record<'string', DeviceList>)  
82 - },  
83 -  
84 getCurrentNodeDataById(config: CreateComponentType): Nullable<NodeDataType> { 68 getCurrentNodeDataById(config: CreateComponentType): Nullable<NodeDataType> {
85 const { cellInfo } = config 69 const { cellInfo } = config
86 const { id } = cellInfo || {} 70 const { id } = cellInfo || {}