530 lines
16 KiB
Vue
530 lines
16 KiB
Vue
<template>
|
|
<div class="measurement">
|
|
<el-table :columns="tableColumn" :data="tableData" @sort-change="sortChange" max-height="495">
|
|
<el-table-column
|
|
v-for="item in tableColumn"
|
|
:key="item.prop"
|
|
:label="item.label"
|
|
:prop="item.prop"
|
|
:width="item.width ?? ''"
|
|
:align="item.align"
|
|
:sortable="item.sortable"
|
|
>
|
|
<template #default="scope">
|
|
<div v-if="item.prop === 'realTimeValue'" class="realTimeValue">
|
|
<div class="realTimeValueText">{{ scope.row.realTimeValue }}</div>
|
|
</div>
|
|
<div v-if="item.prop === 'operate'" @click="openLineChart(scope.row)" class="operate">
|
|
<template v-if="!enumStore.keys.includes(scope.row.attributeCode)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="20px" height="20px" viewBox="0 0 1024 1024" version="1.1">
|
|
{{ scope.row }}
|
|
<path
|
|
d="M896 896H96a32 32 0 0 1-32-32V224a32 32 0 0 1 64 0v608h768a32 32 0 1 1 0 64zM247.008 640a32 32 0 0 1-20.992-56.192l200.992-174.24a32 32 0 0 1 42.272 0.288l172.128 153.44 229.088-246.304a32 32 0 0 1 46.88 43.616l-250.432 269.216a31.936 31.936 0 0 1-44.704 2.08l-174.56-155.52-179.744 155.84a31.872 31.872 0 0 1-20.928 7.776z"
|
|
/>
|
|
</svg>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div>
|
|
<el-pagination
|
|
v-model:current-page="pageSetting.current"
|
|
v-model:page-size="pageSetting.pageSize"
|
|
:total="pageSetting.total"
|
|
:page-sizes="pageSetting.pageSizes"
|
|
background
|
|
:pager-count="7"
|
|
layout="prev, pager, next, jumper,sizes,total"
|
|
@change="getcurrentPage"
|
|
></el-pagination>
|
|
</div>
|
|
</div>
|
|
<el-dialog v-model="lineChartVisible" title="历史曲线" @close="closeLineChart" :width="910">
|
|
<el-form :inline="true" :model="seachOptions" :rules="searchRules" ref="searchFormRef">
|
|
<div class="searchPart">
|
|
<div>
|
|
<el-form-item prop="datePickerValue" label="历史区间:">
|
|
<el-date-picker
|
|
v-model="seachOptions.datePickerValue"
|
|
type="datetimerange"
|
|
start-placeholder="开始时间"
|
|
end-placeholder="结束时间"
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
date-format="YYYY/MM/DD"
|
|
time-format="HH:mm:ss"
|
|
:shortcuts="shortcuts"
|
|
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
|
></el-date-picker>
|
|
</el-form-item>
|
|
</div>
|
|
<div>
|
|
<el-form-item prop="interval" label="时间间隔:">
|
|
<el-select v-model="seachOptions.interval" placeholder="请选择时间间隔" style="width: 100px">
|
|
<el-option label="原始" value="40s"></el-option>
|
|
<el-option label="1分钟" value="1m"></el-option>
|
|
<el-option label="5分钟" value="5m"></el-option>
|
|
<el-option label="10分钟" value="10m"></el-option>
|
|
<el-option label="15分钟" value="15m"></el-option>
|
|
<el-option label="1小时" value="1h"></el-option>
|
|
<el-option label="1天" value="1d"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
</div>
|
|
<el-button type="primary" @click="getChartData" :loading="loading">查询</el-button>
|
|
</div>
|
|
</el-form>
|
|
<div class="chartPart">
|
|
<div class="lineChart" ref="chartRef"></div>
|
|
</div>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, ref, watch, onMounted } from 'vue'
|
|
import { ElMessage, FormInstance, dayjs } from 'element-plus'
|
|
import type { ModelAttributeFieldsEnums, GetModelAttributeType } from '/@/views/backend/auth/model/type'
|
|
import { ModelAttributeType } from '/@/views/backend/auth/model/type'
|
|
import { getModelAttributeListReq, getRealValueListReq } from '/@/api/backend/deviceModel/request'
|
|
import * as echarts from 'echarts'
|
|
import { getRealValueRangeReq } from '/@/api/backend/deviceModel/request'
|
|
import { useEnumStore } from '/@/stores/enums'
|
|
const enumStore = useEnumStore()
|
|
|
|
const props = withDefaults(
|
|
defineProps<{ iotModelId: string; deviceId: string; show: boolean; autoUpdate: boolean; attributeType: ModelAttributeType }>(),
|
|
{
|
|
iotModelId: '',
|
|
deviceId: '',
|
|
show: false,
|
|
autoUpdate: false,
|
|
attributeType: 138,
|
|
}
|
|
)
|
|
|
|
const tableColumn = [
|
|
{
|
|
label: '序号',
|
|
prop: 'porder',
|
|
width: 76,
|
|
align: 'center',
|
|
sortable: 'custom',
|
|
},
|
|
{
|
|
label: '属性名称',
|
|
prop: 'attributeName',
|
|
align: 'left',
|
|
sortable: 'custom',
|
|
},
|
|
{
|
|
label: '属性编码',
|
|
prop: 'attributeCode',
|
|
align: 'left',
|
|
width: 200,
|
|
sortable: 'custom',
|
|
},
|
|
{
|
|
label: '子系统',
|
|
prop: 'subSystem',
|
|
align: 'left',
|
|
width: 110,
|
|
sortable: false,
|
|
},
|
|
{
|
|
label: '实时值',
|
|
prop: 'realTimeValue',
|
|
width: 140,
|
|
align: 'center',
|
|
sortable: false,
|
|
},
|
|
{
|
|
label: '历史曲线',
|
|
prop: 'operate',
|
|
align: 'center',
|
|
width: 80,
|
|
sortable: false,
|
|
},
|
|
]
|
|
const tableData = ref<any[]>([])
|
|
|
|
const getAttributeList = () => {
|
|
const requestData: GetModelAttributeType = {
|
|
iotModelId: props.iotModelId,
|
|
pageNum: pageSetting.current,
|
|
pageSize: pageSetting.pageSize,
|
|
orderColumn: sortData.orderColumn,
|
|
orderType: sortData.orderType,
|
|
attributeType: props.attributeType,
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
getModelAttributeListReq(requestData)
|
|
.then((res) => {
|
|
if (res.rows && res.rows.length > 0) {
|
|
const codeList: any = []
|
|
const data = res.rows!.map((item) => {
|
|
codeList.push(item.attributeCode)
|
|
return {
|
|
...item,
|
|
attributeTypeName:
|
|
item.attributeType === 138
|
|
? '模拟量'
|
|
: item.attributeType === 139
|
|
? '累积量'
|
|
: item.attributeType === 140
|
|
? '离散量'
|
|
: item.attributeType!,
|
|
}
|
|
})
|
|
pageSetting.total = res.total
|
|
resolve({ data, codeList })
|
|
} else {
|
|
if (res.rows && res.rows.length === 0) {
|
|
tableData.value = []
|
|
} else {
|
|
ElMessage.error(res.msg)
|
|
}
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
ElMessage.error(err?.response?.data?.msg ?? '查询失败')
|
|
})
|
|
})
|
|
}
|
|
|
|
const getRealValueList = (data: { deviceId: string; attributes: string[] }, list?: any) => {
|
|
return new Promise((resolve) => {
|
|
getRealValueListReq([data]).then((res) => {
|
|
if (res.success && res.data) {
|
|
resolve({ realVal: res.data, list })
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
const getCompleteData = () => {
|
|
getAttributeList()
|
|
.then(({ data, codeList }: any) => {
|
|
return getRealValueList({ deviceId: props.deviceId, attributes: codeList }, data)
|
|
})
|
|
.then((realData: any) => {
|
|
console.log(realData)
|
|
|
|
const data = realData.list.map((item: any) => {
|
|
let realValItem = realData.realVal[props.deviceId]?.[item.attributeCode?.toLowerCase()]
|
|
if (enumStore.keys.includes(item.attributeCode)) {
|
|
realValItem = enumStore.data[item.attributeCode][realValItem]
|
|
}
|
|
return {
|
|
...item,
|
|
realTimeValue:
|
|
typeof realValItem === 'number'
|
|
? realValItem
|
|
? realValItem % 1 === 0
|
|
? realValItem
|
|
: realValItem.toFixed(3)
|
|
: '-'
|
|
: realValItem,
|
|
}
|
|
})
|
|
|
|
tableData.value = data
|
|
})
|
|
}
|
|
|
|
const sortData = reactive<{ orderColumn?: keyof typeof ModelAttributeFieldsEnums; orderType?: 'asc' | 'desc' }>({
|
|
orderColumn: 'porder',
|
|
orderType: 'asc',
|
|
})
|
|
|
|
const sortChange = ({ prop, order }: { prop: keyof typeof ModelAttributeFieldsEnums; order: 'ascending' | 'descending' | null }) => {
|
|
const propEnums = {
|
|
attributeCode: 'attribute_code',
|
|
attributeName: 'attribute_name',
|
|
attributeTypeName: 'attribute_type',
|
|
porder: 'porder',
|
|
serviceCode: 'service_code',
|
|
serviceName: 'service_name',
|
|
serviceTypeName: 'service_type',
|
|
}
|
|
const orderType = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : undefined
|
|
const filed = propEnums[prop as keyof typeof propEnums] as keyof typeof ModelAttributeFieldsEnums
|
|
sortData.orderColumn = orderType ? filed : undefined
|
|
sortData.orderType = orderType
|
|
getCompleteData()
|
|
}
|
|
|
|
const pageSetting = reactive({
|
|
current: 1,
|
|
pageSize: 20,
|
|
total: 0,
|
|
pageSizes: [20, 50, 100],
|
|
})
|
|
|
|
const getcurrentPage = () => {
|
|
getCompleteData()
|
|
}
|
|
|
|
const openLineChart = (data: any) => {
|
|
lineChartVisible.value = true
|
|
searchInfo.attr = data.attributeCode
|
|
searchInfo.name = data.attributeName
|
|
searchInfo.unit = data.unit
|
|
}
|
|
|
|
watch(
|
|
() => props.show,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
getCompleteData()
|
|
} else {
|
|
autoUpdateTimer.value && clearInterval(autoUpdateTimer.value)
|
|
autoUpdateTimer.value = null
|
|
}
|
|
},
|
|
{
|
|
immediate: true,
|
|
}
|
|
)
|
|
const autoUpdateTimer: any = ref(null)
|
|
watch(
|
|
() => props.autoUpdate,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
if (!autoUpdateTimer.value) {
|
|
autoUpdateTimer.value = setInterval(() => {
|
|
getCompleteData()
|
|
}, 2000)
|
|
}
|
|
} else {
|
|
clearInterval(autoUpdateTimer.value)
|
|
autoUpdateTimer.value = null
|
|
}
|
|
}
|
|
)
|
|
|
|
const loading = ref(false)
|
|
const searchFormRef = ref<FormInstance>()
|
|
const lineChartVisible = ref(false)
|
|
const searchInfo = reactive({
|
|
unit: '',
|
|
name: '',
|
|
attr: '',
|
|
})
|
|
const shortcuts = [
|
|
{
|
|
text: '今天',
|
|
value: () => {
|
|
const start = dayjs().startOf('day').toDate()
|
|
const end = dayjs().endOf('day').toDate()
|
|
return [start, end]
|
|
},
|
|
},
|
|
{
|
|
text: '昨天',
|
|
value: () => {
|
|
const start = dayjs().subtract(1, 'day').startOf('day').toDate()
|
|
const end = dayjs().subtract(1, 'day').endOf('day').toDate()
|
|
return [start, end]
|
|
},
|
|
},
|
|
{
|
|
text: '前三天',
|
|
value: () => {
|
|
const start = dayjs().subtract(4, 'day').startOf('day').toDate()
|
|
const end = dayjs().subtract(1, 'day').endOf('day').toDate()
|
|
return [start, end]
|
|
},
|
|
},
|
|
]
|
|
const getChartData = () => {
|
|
searchFormRef.value?.validate((valid) => {
|
|
if (valid) {
|
|
loading.value = true
|
|
getRealValueRangeReq({
|
|
startTime: dayjs(seachOptions.datePickerValue[0]).valueOf(),
|
|
endTime: dayjs(seachOptions.datePickerValue[1]).valueOf(),
|
|
devices: [
|
|
{
|
|
deviceId: props.deviceId,
|
|
attributes: [searchInfo.attr],
|
|
},
|
|
],
|
|
interval: seachOptions.interval,
|
|
}).then((res) => {
|
|
initChart(res.data?.[props.deviceId]?.[searchInfo.attr])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
const chartRef = ref()
|
|
let chartInstance: any = null
|
|
const seachOptions = reactive({
|
|
datePickerValue: [0, 0],
|
|
interval: '5m',
|
|
})
|
|
|
|
const searchRules = {
|
|
datePickerValue: [
|
|
{
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
if (!value[0] || !value[1]) {
|
|
callback(new Error('请选择时间范围'))
|
|
return
|
|
}
|
|
callback()
|
|
},
|
|
trigger: 'change',
|
|
},
|
|
],
|
|
interval: [
|
|
{
|
|
required: true,
|
|
message: '请输入时间间隔',
|
|
trigger: 'input',
|
|
},
|
|
],
|
|
}
|
|
|
|
const initChart = (data: { values: number[]; times: number[] }) => {
|
|
chartInstance && chartInstance.clear()
|
|
|
|
chartInstance = chartInstance ? chartInstance : echarts.init(chartRef.value)
|
|
const times = data?.times.map((item) => dayjs(item).format('YYYY-MM-DD HH:mm:ss'))
|
|
|
|
const option = {
|
|
animation: false,
|
|
grid: {
|
|
top: 50,
|
|
right: 23,
|
|
bottom: 10,
|
|
left: 18,
|
|
containLabel: true,
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow',
|
|
},
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: '#dadada',
|
|
width: 1,
|
|
type: 'solid',
|
|
},
|
|
},
|
|
axisLabel: {
|
|
//x轴文字的配置
|
|
show: true,
|
|
color: '#4E5969',
|
|
interval: 'auto',
|
|
//rotate: 45
|
|
},
|
|
splitLine: {
|
|
//分割线配置
|
|
show: false,
|
|
lineStyle: {
|
|
color: '#999999',
|
|
},
|
|
},
|
|
data: times ?? [],
|
|
},
|
|
yAxis: [
|
|
{
|
|
type: 'value',
|
|
name: searchInfo.unit,
|
|
nameTextStyle: {
|
|
color: '#4E5969',
|
|
},
|
|
axisLine: {
|
|
show: false,
|
|
lineStyle: {
|
|
color: '#dadada',
|
|
width: 0,
|
|
type: 'solid',
|
|
},
|
|
},
|
|
axisLabel: {
|
|
//x轴文字的配置
|
|
show: true,
|
|
color: '#4E5969',
|
|
},
|
|
axisTick: { show: false },
|
|
splitLine: {
|
|
interval: 50,
|
|
lineStyle: {
|
|
type: 'dashed',
|
|
color: '#dadada',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
legend: {
|
|
data: [searchInfo.name],
|
|
textStyle: {
|
|
color: '#73767a',
|
|
},
|
|
},
|
|
series: [
|
|
{
|
|
name: searchInfo.name,
|
|
type: 'line',
|
|
barWidth: 20,
|
|
itemStyle: {
|
|
color: '#00A7EB',
|
|
barBorderRadius: 2,
|
|
},
|
|
smooth: 0.6,
|
|
symbol: 'none',
|
|
data: data?.values ?? []
|
|
},
|
|
],
|
|
}
|
|
chartInstance.setOption(option)
|
|
loading.value = false
|
|
}
|
|
|
|
const closeLineChart = () => {
|
|
chartInstance && chartInstance.clear()
|
|
searchFormRef.value!.resetFields()
|
|
}
|
|
|
|
watch(
|
|
() => props.attributeType,
|
|
() => {
|
|
getCompleteData()
|
|
}
|
|
)
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.realTimeValueText {
|
|
color: #0064aa;
|
|
}
|
|
|
|
.operate {
|
|
&:hover {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
.searchPart {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
.chartPart {
|
|
width: 100%;
|
|
height: 400px;
|
|
.lineChart {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
</style>
|