Selaa lähdekoodia

feat: 增加项目概览页面的数据及显示逻辑,样式还未优化

yanglzh 9 kuukautta sitten
vanhempi
sitoutus
d5fc8be3f8

+ 18 - 1
src/api/projects/index.ts

@@ -29,5 +29,22 @@ export default {
       editIsVisible: (data: object) => put('/projects/devDeviceAttributeValues/editIsVisible', data),
       detail: (id: string) => get('/projects/devDeviceAttributeValues/getById', { id }),
     }
-  }
+  },
+  screen: {
+    devParts: (params?: object) => get('projects/devParts/list', params),
+    projects: (params?: object) => get('/projects/list', params),
+    projectDevices: (projectsCode: string) => get('/projects/getProjectResourcesByCode', { projectsCode }),
+    getProjectInfoByCode: (propertiyKeys: string[] = []) => get('/projects/screen/getProjectInfoByCode', { propertiyKeys, status: 1 }),
+    projectDevicesList: (keys: string[]) => get('/product/device/page_list', { keys, pageSize: 500 }),
+    projectDetail: (code: string) => get('/projects/getByCode', { code }),
+    deviceDetail: (deviceKey: string) => get('/projects/screen/getDeviceInfoByKey', { deviceKey }),
+    propertyList: (deviceKey: string) => get('/projects/device/tsl/property/list', { deviceKey, pageSize: 500 }),
+    propertyListValue: (projectCode: string, deviceKey: string) => get('/projects/screen/getDeviceDetailDataByLatest', { projectCode, deviceKey }),
+    deviceList: (projectsCode?: string) => get('/projects/getProjectDeviceByCode', { projectsCode }),
+    chartData: (params: object) => get('/analysis/multiAttributeDeviceIndicatorTrend', params),
+    deviceInfo: (deviceKey: string) => get('/product/device/detail', { deviceKey }),
+    alarmCount: (deviceKey?: string) => get('/projects/screen/deviceAlarmTotalCount', { deviceKey }),
+    statistics: (params?: object) => get('/projects/screen/getBoilerStatistics', params),
+    getDeviceDetailDataByLatest: (params?: object) => get('/projects/screen/getDeviceDetailDataByLatest', params),
+  },
 }

BIN
src/assets/info-bg.png


BIN
src/assets/video-bg.png


+ 89 - 0
src/components/chart/options.ts

@@ -482,3 +482,92 @@ export function getBarRowOption({
     ]
   };
 }
+
+const defaultTooltip = {
+  trigger: 'axis',
+  appendToBody: true,
+  backgroundColor: '#082046',
+  borderColor: '#26689F',
+  textStyle: {
+    color: '#fff'
+  }
+}
+
+const defaultLegend = {
+  left: 'center',
+  top: 0,
+  appendToBody: true,
+  textStyle: {
+    color: '#fff',
+    fontSize: getPx(12)
+  },
+}
+
+export function getLineMultiOption({
+  datas = [[12, 14, 0]] as any[],
+  xAxis = ['4', '5', '6'] as any[],
+  legend = [] as string[],
+  dataZoom = [] as object[],
+}) {
+
+  const series = datas.map((data, i) => {
+    return {
+      data,
+      type: 'line',
+      symbol: 'circle',
+      name: legend[i],
+      smooth: true
+    }
+  })
+
+  return {
+    tooltip: defaultTooltip,
+    legend: {
+      data: legend,
+      ...defaultLegend
+    },
+    grid: { top: getPx(30), bottom: 0, left: getPx(45), right: getPx(30), containLabel: true },
+    xAxis: {
+      type: 'category',
+      data: xAxis,
+      boundaryGap: false,
+      splitLine: {
+        show: true,
+        lineStyle: {
+          color: '#353E4E',
+          type: 'dashed',
+        }
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#5D6771',
+        }
+      },
+      axisTick: { show: false },
+      axisLabel: {
+        show: true,
+        fontSize: getPx(15),
+        color: '#CCE2F1'
+      },
+    },
+    yAxis: {
+      type: 'value',
+      splitLine: { show: false },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#5D6771'
+        }
+      },
+      axisTick: { show: false },
+      axisLabel: {
+        show: true,
+        fontSize: getPx(15),
+        color: '#CCE2F1'
+      },
+    },
+    series,
+    dataZoom
+  };
+}

+ 131 - 0
src/views/iot/projects/screen/BaseinfoVue.vue

@@ -0,0 +1,131 @@
+<template>
+	<div class="select-device">
+		选择设备:
+		<el-select style="width: 15vw" v-model="deviceKey" @change="deviceChnage" placeholder="">
+			<el-option v-for="row in deviceList" :key="row.key" :label="row.name" :value="row.key"></el-option>
+		</el-select>
+	</div>
+	<section class="baseinfo" v-loading="loading">
+		<div class="flex-item" v-for="row in propertiyList" :key="row.devicePropertiyKey">
+			<div class="label">{{ row.devicePropertiyName }}</div>
+			<div class="val">
+				{{ row.devicePropertiyValue }}
+				<span class="unit" v-if="row.devicePropertiyUnit">{{ row.devicePropertiyUnit }}</span>
+			</div>
+		</div>
+		<template v-if="propertiyList.length % 3">
+			<div class="flex-item space" v-for="i in 3 - (propertiyList.length % 3)" :key="i"></div>
+		</template>
+	</section>
+</template>
+<script setup lang="ts">
+import { ref, inject, watch, Ref } from 'vue'
+import api from '/@/api/projects'
+
+const emit = defineEmits(['change'])
+
+const projectCode = inject<Ref<string>>('projectCode', ref(''))
+
+const loading = ref(true)
+const deviceKey = ref('')
+const deviceList = ref<any[]>([])
+const propertiyList = ref<any[]>([])
+
+watch(() => projectCode.value, getData)
+
+function getData(code: string) {
+	if (!code) return
+
+	loading.value = true
+
+	deviceKey.value = ''
+	deviceList.value = []
+	propertiyList.value = []
+
+	api.screen.projectDevices(code).then((res: any) => {
+		if (!res?.length) return (loading.value = false)
+
+		const keys = (res || []).filter((row: any) => row.resourcesTypes === 1).map((item: any) => item.resourcesKey)
+
+		loading.value = true
+		api.screen.projectDevicesList(keys).then((res: any) => {
+			const list = res.device || []
+
+			deviceList.value = list
+
+			if (list.length) {
+				deviceKey.value = list[0].key
+				deviceChnage()
+			} else {
+				emit('change', '')
+				loading.value = false
+			}
+		})
+	})
+}
+
+function deviceChnage() {
+	emit('change', deviceKey.value)
+
+	loading.value = true
+	api.screen
+		.propertyListValue(projectCode.value, deviceKey.value)
+		.then((res: any) => {
+			propertiyList.value = res || []
+		})
+		.finally(() => (loading.value = false))
+}
+</script>
+
+<style lang="scss" scoped>
+.select-device {
+	display: flex;
+	align-items: center;
+	font-size: 1.5vh;
+	font-weight: 500;
+}
+
+.baseinfo {
+	background-size: 100% 100%;
+	margin-top: 1.5vh;
+	display: flex;
+	flex-flow: row wrap;
+	justify-content: space-between;
+	gap: 2vh;
+	height: 20vh;
+	overflow-y: auto;
+
+	.flex-item {
+		background-size: 100% 100%;
+		display: flex;
+		align-items: center;
+		flex: 1;
+		height: 3.5vh;
+		max-width: 33%;
+		min-width: 30%;
+		text-indent: 1.1em;
+		font-size: 1.55vh;
+		white-space: nowrap;
+		overflow: hidden;
+
+		&.space {
+			background: none;
+		}
+
+		.label {
+			flex: 1;
+		}
+
+		.val {
+			font-size: 2.2vh;
+			font-weight: bold;
+			flex: 1;
+
+			.unit {
+				font-weight: normal;
+				font-size: 1.4vh;
+			}
+		}
+	}
+}
+</style>

+ 95 - 0
src/views/iot/projects/screen/InfoVue.vue

@@ -0,0 +1,95 @@
+<template>
+	<div class="title">
+		选择项目:
+		<el-select style="width: 15vw" v-model="project" @change="projectChange" placeholder="">
+			<el-option v-for="row in projectList" :key="row.id" :label="row.name" :value="row.code"></el-option>
+		</el-select>
+	</div>
+	<section class="info">
+		<div class="info-content" v-if="projectData?.name">
+			<div class="flex-row">
+				<div class="flex-item">
+					<div class="label">项目名称:</div>
+					<div class="val">{{ projectData.name }}</div>
+				</div>
+				<div class="flex-item">
+					<div class="label">项目地址:</div>
+					<div class="val">{{ projectData.addressDetail }}</div>
+				</div>
+			</div>
+			<div class="flex-row">
+				<div class="flex-item">
+					<div class="label">供热面积:</div>
+					<div class="val">{{ projectData.heatingArea }} ㎡</div>
+				</div>
+				<div class="flex-item">
+					<div class="label">设备台数:</div>
+					<div class="val">{{ projectData.deviceCount }} 台</div>
+				</div>
+				<div class="flex-item">
+					<div class="label">维保电话:</div>
+					<div class="val">{{ projectData.repairMobile }}</div>
+				</div>
+			</div>
+		</div>
+	</section>
+</template>
+<script setup lang="ts">
+import { reactive, ref, inject, watch, Ref } from 'vue'
+
+const emit = defineEmits(['change'])
+
+const props = defineProps(['projectList'])
+const projectCode = inject<Ref<string>>('projectCode', ref(''))
+
+const project = ref(projectCode)
+const projectData = ref<any>({})
+
+watch(() => projectCode.value, projectChange)
+
+function projectChange(code: string) {
+	project.value = code
+	emit('change', code)
+	projectData.value = props.projectList.find((row: any) => row.code === code) || {}
+}
+</script>
+
+<style lang="scss" scoped>
+.title {
+	color: var(--primary-color);
+	font-size: 1.5vh;
+	height: 4.2vh;
+	display: flex;
+	align-items: center;
+	font-weight: 500;
+}
+
+.info {
+	height: 11.2vh;
+	display: flex;
+	align-items: center;
+
+	.info-content {
+		flex: 1;
+		height: 9vh;
+		display: flex;
+		flex-flow: column nowrap;
+		justify-content: space-around;
+		font-size: 1.45vh;
+
+		.flex-item {
+			display: flex;
+			align-items: center;
+
+			.label {
+				color: var(--primary-color);
+			}
+
+			.val {
+				font-size: 1.65vh;
+				font-weight: 500;
+			}
+		}
+	}
+}
+</style>

+ 125 - 0
src/views/iot/projects/screen/LineChart.vue

@@ -0,0 +1,125 @@
+<template>
+	<div class="select-device">
+		选择时间:
+		<el-date-picker
+			class="date-picker-wrap"
+			v-model="params.timeRange"
+			:clearable="false"
+			type="datetimerange"
+			range-separator="至"
+			start-placeholder="开始时间"
+			end-placeholder="结束时间"
+			format="MM-DD HH:mm"
+			value-format="YYYY-MM-DD HH:mm:00"
+			date-format="YYYY/MM/DD"
+			time-format="hh:mm"
+			@change="getChartData"
+			style="width: 12.5vw; margin-right: 1vw"
+		/>
+		选择参数:
+		<el-select style="width: 12vw" v-model="params.properties" multiple collapse-tags @change="getChartData" placeholder="">
+			<el-option v-for="row in options" :key="row.key" :label="row.name" :value="row.key"></el-option>
+		</el-select>
+	</div>
+	<section class="line">
+		<Chart height="22.5vh" ref="chartRef"></Chart>
+	</section>
+</template>
+<script setup lang="ts">
+import { onMounted, reactive, ref, watch } from 'vue'
+import { getLineMultiOption } from '/@/components/chart/options'
+import Chart from '/@/components/chart/index.vue'
+import api from '/@/api/projects'
+import { dayjs } from 'element-plus'
+
+const props = defineProps({
+	index: {
+		type: [String, Number],
+		dafault: 0,
+	},
+	deviceKey: {
+		type: String,
+		dafault: '',
+	},
+})
+
+let deviceCode = ''
+const chartRef = ref()
+const options = ref<any[]>([])
+const params = reactive({
+	timeRange: [dayjs().subtract(1, 'hour').format('YYYY-MM-DD HH:mm:00'), dayjs().format('YYYY-MM-DD HH:mm:00')],
+	properties: [] as string[],
+})
+
+watch(() => props.deviceKey!, getData)
+
+onMounted(() => {
+	chartRef.value?.loading()
+})
+
+function getData(code: string) {
+	params.properties = []
+	options.value = []
+
+	deviceCode = code
+
+	if (!code) return chartRef.value.draw()
+
+	chartRef.value?.loading()
+
+	api.screen.propertyList(code).then((res: any) => {
+		const list = res?.Data || []
+		options.value = list
+
+		if (!list.length) return chartRef.value.draw()
+
+		const i = Number(props.index)
+		if (list[i]) {
+			params.properties = [list[i].key]
+		} else {
+			params.properties = [list[0].key]
+		}
+		getChartData()
+	})
+}
+
+function getChartData() {
+	chartRef.value.loading()
+	if (!params.properties.length) return chartRef.value.draw()
+	api.screen
+		.chartData({
+			deviceKey: deviceCode,
+			dateRange: params.timeRange,
+			properties: params.properties,
+		})
+		.then((res: any) => {
+			if (!res) return chartRef.value.draw()
+
+			const values = Object.values(res) as any[]
+
+			const legend = Object.keys(res).map((key) => options.value.find((item) => item.key === key)?.name)
+
+			chartRef.value?.draw(
+				getLineMultiOption({
+					datas: values.map((arr: any) => (arr || []).map((item: any) => item.dataValue)),
+					legend,
+					xAxis: (values[0] || []).map((item: any) => item.dataTime),
+				})
+			)
+		})
+}
+</script>
+
+<style lang="scss" scoped>
+.select-device {
+	color: var(--primary-color);
+	display: flex;
+	align-items: center;
+	white-space: nowrap;
+	font-size: 1.5vh;
+	font-weight: 500;
+}
+
+.line {
+}
+</style>

+ 110 - 0
src/views/iot/projects/screen/VideoVue.vue

@@ -0,0 +1,110 @@
+<template>
+	<section class="video" v-loading="loading">
+		<div class="title">
+			<div class="label">选择项目:</div>
+			<el-select class="flex1" v-model="params.project" @change="projectChange" placeholder="">
+				<el-option v-for="row in projectList" :key="row.id" :label="row.name" :value="row.code"></el-option>
+			</el-select>
+			<!-- <el-select v-model="params.type" placeholder="" style="width: 9vw; margin-left: 1.6vw">
+				<el-option label="视频监控" value="1"></el-option>
+				<el-option label="组态" value="2"></el-option>
+			</el-select> -->
+		</div>
+		<div class="video-content">
+			<template v-if="params.type === '2' && topoSrc">
+				<div class="full-screent-icon" @click="fullScreen = true">
+					<FullScreen />
+				</div>
+				<iframe :src="topoSrc" frameborder="0"></iframe>
+			</template>
+		</div>
+		<el-dialog v-model="fullScreen" class="video-screen-dialog" fullscreen append-to-body>
+			<iframe v-if="fullScreen" :src="topoSrc" frameborder="0"></iframe>
+		</el-dialog>
+	</section>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, inject, watch, Ref } from 'vue'
+import api from '/@/api/projects'
+
+const emit = defineEmits(['change'])
+
+const { projectList } = defineProps(['projectList'])
+const projectCode = inject<Ref<string>>('projectCode', ref(''))
+const fullScreen = ref(false)
+const loading = ref(false)
+const topoSrc = ref('')
+
+watch(() => projectCode.value, projectChange)
+
+const params = reactive({
+	project: projectCode,
+	type: '2',
+})
+
+function projectChange(code: string) {
+	topoSrc.value = ''
+  loading.value = true
+
+  emit('change', code) 
+
+	api.screen
+		.projectDevices(code)
+		.then((res: any) => {
+			const topoKey = (res || []).find((row: any) => row.resourcesTypes === 2)?.resourcesKey
+			// topoSrc.value = topoKey ? '/plugin/topo/#/show/' + topoKey : ''
+			topoSrc.value = topoKey ? 'https://zhgy.sagoo.cn/plugin/topo/index.html#/show/' + topoKey : ''
+		})
+		.finally(() => (loading.value = false))
+}
+</script>
+<style lang="scss" scoped>
+iframe {
+	width: 100%;
+	height: 100%;
+	padding: 0;
+	overflow: hidden;
+}
+
+.video {
+	height: 48.5vh;
+	pointer-events: all;
+	background-size: 100% 100%;
+	background-repeat: no-repeat;
+	background-position: center;
+
+	.video-content {
+		width: 100%;
+		height: 43vh;
+		padding: 0 2px;
+		border-radius: 2vh;
+		margin-top: 1vh;
+		position: relative;
+
+		.full-screent-icon {
+			position: absolute;
+			right: 1.5vh;
+			top: -2.5vh;
+			width: 2vh;
+			height: 2vh;
+			color: var(--primary-color);
+			cursor: pointer;
+		}
+	}
+
+	.title {
+		color: var(--primary-color);
+		font-size: 1.7vh;
+		height: 4.2vh;
+		display: flex;
+		align-items: center;
+		padding: 0 12vw 0 4vw;
+		justify-content: space-between;
+
+		.label {
+			font-weight: 500;
+		}
+	}
+}
+</style>

+ 68 - 0
src/views/iot/projects/screen/index.vue

@@ -0,0 +1,68 @@
+<script setup lang="ts">
+import LineChart from './LineChart.vue'
+import VideoVue from './VideoVue.vue'
+import InfoVue from './InfoVue.vue'
+import BaseinfoVue from './BaseinfoVue.vue'
+import { provide, ref, nextTick } from 'vue'
+import api from '/@/api/projects'
+
+const projectCode = ref('')
+const deviceKey = ref('')
+const projectList = ref<any[]>([])
+
+provide('projectCode', projectCode)
+
+api.screen
+	.projects({
+		pageNum: 1,
+		pageSize: 500,
+		status: 1,
+	})
+	.then(({ list }: { list: any[] }) => {
+		projectList.value = list || []
+		console.log(list)
+		if (list?.length) {
+			setTimeout(() => {
+				projectCode.value = list[0].code
+			}, 1000)
+		}
+	})
+</script>
+
+<template>
+	<section class="container">
+		<div class="part">
+			<VideoVue :projectList="projectList" @change="projectCode = $event"></VideoVue>
+			<LineChart :deviceKey="deviceKey" index="0"></LineChart>
+		</div>
+		<div class="part">
+			<InfoVue :projectList="projectList" @change="projectCode = $event"></InfoVue>
+			<BaseinfoVue @change="deviceKey = $event"></BaseinfoVue>
+			<LineChart :deviceKey="deviceKey" index="1"></LineChart>
+		</div>
+	</section>
+</template>
+
+<style lang="scss" scoped>
+.container {
+	width: 100%;
+	display: flex;
+	justify-content: space-between;
+	height: 100%;
+	overflow: hidden;
+	gap: 1.3vw;
+	margin-top: 1.7vh;
+
+	.part {
+		flex: 1;
+		display: flex;
+		flex-flow: column nowrap;
+		justify-content: space-between;
+		gap: var(--gap);
+		z-index: 10;
+		overflow: hidden;
+		margin-bottom: 8.5vh;
+		overflow: hidden;
+	}
+}
+</style>