Browse Source

feat: 折线数据增加系数运算

main
betaqi 2 months ago
parent
commit
8895c1cc04
  1. 28
      package-lock.json
  2. 2
      package.json
  3. 197
      src/views/stationData/component/line-dlg.vue
  4. 69
      src/views/stationData/component/newDataChart.vue
  5. 26
      src/views/stationData/topology/components/detailDrawer.vue
  6. 61
      src/views/stationData/transfer/components/deviceDrawer.vue

28
package-lock.json generated

@ -14,6 +14,7 @@
"@types/qs": "^6.9.18", "@types/qs": "^6.9.18",
"@unocss/reset": "^66.0.0", "@unocss/reset": "^66.0.0",
"axios": "^1.8.4", "axios": "^1.8.4",
"big.js": "^7.0.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dexie": "^4.0.11", "dexie": "^4.0.11",
"echarts": "^5.6.0", "echarts": "^5.6.0",
@ -32,6 +33,7 @@
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.310", "@iconify/json": "^2.2.310",
"@tsconfig/node22": "^22.0.0", "@tsconfig/node22": "^22.0.0",
"@types/big.js": "^6.2.2",
"@types/node": "^24.2.1", "@types/node": "^24.2.1",
"@unocss/preset-icons": "^66.0.0", "@unocss/preset-icons": "^66.0.0",
"@unocss/preset-rem-to-px": "^66.0.0", "@unocss/preset-rem-to-px": "^66.0.0",
@ -2211,6 +2213,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/big.js": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.2.2.tgz",
"integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/d3-array": { "node_modules/@types/d3-array": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
@ -3680,12 +3689,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/big.js": { "node_modules/big.js": {
"version": "5.2.2", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "resolved": "https://registry.npmjs.org/big.js/-/big.js-7.0.1.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "integrity": "sha512-iFgV784tD8kq4ccF1xtNMZnXeZzVuXWWM+ERFzKQjv+A5G9HC8CY3DuV45vgzFFcW+u2tIvmF95+AzWgs6BjCg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "*" "node": "*"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/bigjs"
} }
}, },
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
@ -5724,6 +5737,15 @@
"node": ">=8.9.0" "node": ">=8.9.0"
} }
}, },
"node_modules/loader-utils/node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/local-pkg": { "node_modules/local-pkg": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz",

2
package.json

@ -20,6 +20,7 @@
"@types/qs": "^6.9.18", "@types/qs": "^6.9.18",
"@unocss/reset": "^66.0.0", "@unocss/reset": "^66.0.0",
"axios": "^1.8.4", "axios": "^1.8.4",
"big.js": "^7.0.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dexie": "^4.0.11", "dexie": "^4.0.11",
"echarts": "^5.6.0", "echarts": "^5.6.0",
@ -38,6 +39,7 @@
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.310", "@iconify/json": "^2.2.310",
"@tsconfig/node22": "^22.0.0", "@tsconfig/node22": "^22.0.0",
"@types/big.js": "^6.2.2",
"@types/node": "^24.2.1", "@types/node": "^24.2.1",
"@unocss/preset-icons": "^66.0.0", "@unocss/preset-icons": "^66.0.0",
"@unocss/preset-rem-to-px": "^66.0.0", "@unocss/preset-rem-to-px": "^66.0.0",

197
src/views/stationData/component/line-dlg.vue

@ -0,0 +1,197 @@
<template>
<EdfsDialog
class="plf-strategy-dlg"
:title="title"
:isShow="visibility"
width="20%"
@on-close="onClone"
@on-save="onSave"
>
<div class="dlg-body">
<el-row>
<div class="label">设置系数</div>
<div class="box">
<div
class="operator-btn"
:class="{ active: formData.operator === '/' }"
@click="formData.operator = '/'"
>
</div>
<div class="custom-stepper">
<div class="stepper-btn minus" @click="decrease">-</div>
<div class="stepper-input">{{ formData.coefficient }}</div>
<div class="stepper-btn plus" @click="increase">+</div>
</div>
<div
class="operator-btn"
:class="{ active: formData.operator === '*' }"
@click="formData.operator = '*'"
>
</div>
</div>
</el-row>
</div>
</EdfsDialog>
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash'
import EdfsDialog from '@/components/Edfs-dialog.vue'
const emits = defineEmits(['on-save', 'on-close'])
const title = ref()
const createObj = {
id: '',
coefficient: 1,
operator: '*',
}
const formData = ref(cloneDeep(createObj)) as any
function onClone() {
formData.value = cloneDeep(createObj)
visibility.value = false
emits('on-close')
}
async function onSave() {
emits('on-save', cloneDeep(formData.value))
onClone()
}
const visibility = ref(false)
function open(titleStr: string, id: string, coefficient: string, operator: string) {
title.value = titleStr
formData.value.id = id
if (coefficient) {
formData.value.coefficient = Number(coefficient)
}
if (operator) {
formData.value.operator = operator
}
visibility.value = true
}
const ALLOWED_VALUES = [1, 10, 100, 1000]
function decrease() {
const currentIndex = ALLOWED_VALUES.indexOf(formData.value.coefficient)
if (currentIndex > 0) {
formData.value.coefficient = ALLOWED_VALUES[currentIndex - 1]
}
}
function increase() {
const currentIndex = ALLOWED_VALUES.indexOf(formData.value.coefficient)
if (currentIndex < ALLOWED_VALUES.length - 1) {
formData.value.coefficient = ALLOWED_VALUES[currentIndex + 1]
} else if (currentIndex === -1) {
formData.value.coefficient = 1
}
}
defineExpose({
open,
})
</script>
<style scoped lang="scss">
.plf-strategy-dlg {
.dlg-body {
}
.el-row {
height: 32px;
margin-bottom: 20px;
:deep(.label) {
margin-right: 10px;
line-height: 32px;
width: 100px;
text-align: right;
}
span {
margin-right: 10px;
width: 110px;
height: 32px;
line-height: 32px;
text-align: right;
}
:deep(.el-radio-group) {
height: 100%;
}
:deep(.el-radio) {
height: 100%;
}
}
.box {
display: flex;
gap: 10px;
}
.operator-btn {
width: 40px;
height: 32px;
line-height: 32px;
text-align: center;
border: 1px solid var(--el-border-color);
cursor: pointer;
border-radius: var(--el-border-radius-base);
color: var(--el-text-color-regular);
background-color: var(--el-bg-color);
&.active {
background-color: var(--el-color-primary);
color: var(--el-color-white);
border-color: var(--el-color-primary);
}
&:hover:not(.active) {
color: var(--el-color-primary);
border-color: var(--el-color-primary-light-7);
background-color: var(--el-color-primary-light-9);
}
}
.mx-2 {
margin: 0 10px;
}
.custom-stepper {
display: flex;
border: 1px solid var(--el-border-color);
border-radius: var(--el-border-radius-base);
height: 32px;
overflow: hidden;
background-color: var(--el-bg-color);
.stepper-btn {
width: 32px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--el-fill-color-light);
cursor: pointer;
user-select: none;
color: var(--el-text-color-regular);
transition: all 0.3s;
&:hover {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
&.minus {
border-right: 1px solid var(--el-border-color);
}
&.plus {
border-left: 1px solid var(--el-border-color);
}
}
.stepper-input {
flex: 1;
min-width: 50px;
text-align: center;
line-height: 30px;
font-size: 14px;
color: var(--el-text-color-regular);
background-color: var(--el-bg-color);
}
}
}
</style>

69
src/views/stationData/component/newDataChart.vue

@ -7,22 +7,26 @@
:autoresize="autoresize" :autoresize="autoresize"
:loading-options="loadingOpt" :loading-options="loadingOpt"
:loading="loading" :loading="loading"
/> @click="onChartClick"
/>
</div> </div>
<LineChartDlg ref="LineChartDlgRef" @on-save="onChangeCoefficient"/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Big from 'big.js'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import VChart from 'vue-echarts' import VChart from 'vue-echarts'
import { use } from 'echarts/core' import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
import type { EChartsOption, SeriesOption } from 'echarts/types/dist/shared.js' import type { EChartsOption, SeriesOption } from 'echarts/types/dist/shared.js'
import { LineChart } from 'echarts/charts' import { LineChart } from 'echarts/charts'
import LineChartDlg from './line-dlg.vue'
import { import {
TooltipComponent,
LegendComponent,
DataZoomComponent, DataZoomComponent,
GridComponent, GridComponent,
LegendComponent,
TooltipComponent,
} from 'echarts/components' } from 'echarts/components'
use([ use([
@ -35,17 +39,37 @@ use([
]) ])
const ZOOM_HEIGHT = 30 const ZOOM_HEIGHT = 30
const ZOOM_BOTTOM = 10 const ZOOM_BOTTOM = 10
const emits = defineEmits(['on-change-coefficient'])
type Legend = { type Legend = {
addr: string addr: string
label: string label: string
unit?: string unit?: string
coefficient?: string | number
operator?: string
} }
const autoresize = { const autoresize = {
throttle: 0, throttle: 0,
} }
const LineChartDlgRef = ref()
function onChangeCoefficient(data: { id: string; coefficient: string; operator: string }) {
emits('on-change-coefficient', data)
}
const onChartClick = (params: any) => {
// 线
const [seriesName, id] = params?.seriesName?.split('|')
if (params.componentType === 'series' && params.seriesType === 'line' && seriesName && id) {
const find = props.legends.find(item => item.addr === id) as any
if (find) {
LineChartDlgRef.value?.open(seriesName, id, find?.coefficient, find?.operator)
}
}
}
const props = defineProps({ const props = defineProps({
title: String, title: String,
smooth: Boolean, //线 smooth: Boolean, //线
@ -82,14 +106,25 @@ const chartOption = computed<EChartsOption>(() => {
loading.value = false loading.value = false
const tmpSeries: SeriesOption[] = [] const tmpSeries: SeriesOption[] = []
for (const legend of props.legends.filter(item => item.addr !== 'ts')) { for (const legend of props.legends.filter(item => item.addr !== 'ts')) {
const entry = props.chartDatas.get(legend.addr) as Array<any> const entry = props.chartDatas.get(legend.addr) ?? []
const lineData: SeriesOption = { const lineData: SeriesOption = {
name: legend.label, name: `${legend.label}|${legend.addr}`,
type: 'line', type: 'line',
id: legend.addr, id: legend.addr,
// symbol: "none", // symbol: "none",
connectNulls: true, connectNulls: true,
data: entry, triggerLineEvent: true,
data: entry.map(([ts, val]) => {
const coef = legend?.coefficient ? new Big(legend.coefficient) : new Big(1)
const operator = legend?.operator || '*'
let valBig = new Big(val)
if (operator === '/') {
valBig = valBig.div(coef)
} else {
valBig = valBig.times(coef)
}
return [ts, valBig.toNumber()]
}),
sampling: 'lttb', sampling: 'lttb',
animation: false, animation: false,
} }
@ -126,10 +161,13 @@ const chartOption = computed<EChartsOption>(() => {
let relVal = timeStr let relVal = timeStr
for (let i = 0; i < params.length; i++) { for (let i = 0; i < params.length; i++) {
const unit = props.legends.find(item => item.addr === params[i].seriesId)?.unit || '' const data = props.legends.find(item => item.addr === params[i].seriesId)
relVal += `<div style="display: flex; justify-content: space-between; gap: 30px;"> const conefficients = data?.coefficient ?? undefined
const unit = data?.unit || ''
relVal += `<div style="display: flex; justify-content: space-between; gap: 30px;">
<div>${params[i].marker}${params[i].seriesName}:</div> <div>${params[i].marker}${params[i].seriesName}:</div>
<div>${params[i].value[1]}${unit}</div> <div>${params[i].value[1]}${conefficients ? `【系数为${conefficients}(${operatorMap[data?.operator] ?? ''
})` : ''}${unit}</div>
</div>` </div>`
} }
return relVal return relVal
@ -139,17 +177,21 @@ const chartOption = computed<EChartsOption>(() => {
type: 'scroll', type: 'scroll',
orient: 'vertical', orient: 'vertical',
formatter: name => { formatter: name => {
return name.length > 15 ? name.substring(0, 15) + '...' : name const nameStr = name.split('|')[0]
return nameStr.length > 15 ? nameStr.substring(0, 15) + '...' : nameStr
}, },
tooltip: { tooltip: {
show: true, show: true,
formatter: function (params) {
return params.name.split('|')[0]
},
}, },
right: 0, right: 0,
top: 20, top: 20,
itemWidth: 10, itemWidth: 10,
itemHeight: 10, itemHeight: 10,
pageIconSize: 12, pageIconSize: 12,
data: props.legends.map(item => item.label), data: props.legends.map(item => `${item.label}|${item.addr}`),
// selected: unCheckArr.value.reduce((acc, cur) => { // selected: unCheckArr.value.reduce((acc, cur) => {
// acc[cur] = false // acc[cur] = false
// return acc // return acc
@ -220,6 +262,11 @@ const chartOption = computed<EChartsOption>(() => {
const unCheckArr = ref<string[]>([]) const unCheckArr = ref<string[]>([])
const operatorMap = {
'*': '×',
'/': '÷',
}
function changeLegend(data: { name: string; selected: Record<string, boolean> }) { function changeLegend(data: { name: string; selected: Record<string, boolean> }) {
const { name, selected } = data const { name, selected } = data
if (!selected[name]) { if (!selected[name]) {

26
src/views/stationData/topology/components/detailDrawer.vue

@ -79,7 +79,9 @@
:chart-datas="chartData" :chart-datas="chartData"
:legends="legends" :legends="legends"
:axis-data="Array.from(axisData)" :axis-data="Array.from(axisData)"
ref="chartRef"/> ref="chartRef"
@onChangeCoefficient="onChangeCoefficient"
/>
</div> </div>
</main> </main>
</el-drawer> </el-drawer>
@ -249,7 +251,27 @@ async function loadDeviceDetails() {
const chartData = reactive(new Map<string, any[]>()) const chartData = reactive(new Map<string, any[]>())
const axisData = new Set<string>() const axisData = new Set<string>()
const legends = ref<{ addr: string; label: string, unit: string }[]>([]) const legends = ref<{
addr: string; label: string, unit: string, coefficient?: string | number
operator?: string
}[]>([])
function onChangeCoefficient(
{
id,
coefficient,
operator,
}: {
id: string
coefficient: string
operator: string
}) {
const find = legends.value.findIndex(r => r.addr === id)
if (find !== -1) {
legends.value[find].coefficient = coefficient
legends.value[find].operator = operator
}
}
const loadingChart = ref(false) const loadingChart = ref(false)
const chartAllTotal = ref(0) const chartAllTotal = ref(0)

61
src/views/stationData/transfer/components/deviceDrawer.vue

@ -33,28 +33,28 @@
:key="deviceInfo.id" :key="deviceInfo.id"
class="space-y-3 text-center bg-white/95 backdrop-blur-sm p-6 rounded-xl shadow-lg border border-gray-100 hover:shadow-xl transition-shadow duration-200" class="space-y-3 text-center bg-white/95 backdrop-blur-sm p-6 rounded-xl shadow-lg border border-gray-100 hover:shadow-xl transition-shadow duration-200"
> >
<div class="font-semibold text-gray-700 text-lg"> <div class="font-semibold text-gray-700 text-lg">
{{ chartGroupMap?.get(deviceInfo.id)?.cnName }} 数据加载中 {{ chartGroupMap?.get(deviceInfo.id)?.cnName }} 数据加载中
</div> </div>
<el-progress <el-progress
:text-inside="true" :text-inside="true"
:stroke-width="20" :stroke-width="20"
:percentage="deviceInfo.progress" :percentage="deviceInfo.progress"
/> />
<div class="text-gray-600 text-base">已查询 <div class="text-gray-600 text-base">已查询
<span class="text-green-600 font-semibold"> <span class="text-green-600 font-semibold">
{{ deviceInfo.fetchLimit }} {{ deviceInfo.fetchLimit }}
</span>/<span class="text-blue-600 font-semibold">{{ </span>/<span class="text-blue-600 font-semibold">{{
deviceInfo.total deviceInfo.total
}}</span>剩余<span }}</span>剩余<span
class="text-red-500 font-semibold">{{ class="text-red-500 font-semibold">{{
deviceInfo.total - deviceInfo.fetchLimit deviceInfo.total - deviceInfo.fetchLimit
}}</span> }}</span>
</div>
</div> </div>
</div> </div>
</div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</div> </div>
@ -79,7 +79,8 @@
小时 小时
</div> </div>
<NewDataChart v-if="isShowChart" :chart-datas="chartData" :legends="legends" <NewDataChart v-if="isShowChart" :chart-datas="chartData" :legends="legends"
:axis-data="Array.from(axisData)" ref="chartRef"/> :axis-data="Array.from(axisData)" ref="chartRef"
@onChangeCoefficient="onChangeCoefficient"/>
</div> </div>
</main> </main>
</el-drawer> </el-drawer>
@ -243,7 +244,10 @@ async function loadDeviceDetails() {
const chartData = reactive(new Map<string, any[]>()) const chartData = reactive(new Map<string, any[]>())
const axisData = new Set<string>() const axisData = new Set<string>()
const legends = ref<{ addr: string; label: string, unit: string }[]>([]) const legends = ref<{
addr: string; label: string, unit: string, coefficient?: string | number
operator?: string
}[]>([])
const loadingChart = ref(false) const loadingChart = ref(false)
const loading = ref(false) const loading = ref(false)
@ -333,6 +337,23 @@ function setChartData2(groupName: string, data: any[]) {
}) })
} }
function onChangeCoefficient(
{
id,
coefficient,
operator,
}: {
id: string
coefficient: string
operator: string
}) {
const find = legends.value.findIndex(r => r.addr === id)
if (find !== -1) {
legends.value[find].coefficient = coefficient
legends.value[find].operator = operator
}
}
function handleBeforeClose(done: () => void) { function handleBeforeClose(done: () => void) {
ElMessageBox.confirm('你确定要关闭吗?') ElMessageBox.confirm('你确定要关闭吗?')
.then(() => { .then(() => {

Loading…
Cancel
Save