This commit is contained in:
2026-02-13 21:46:12 +08:00
parent 7fdb6e2ad3
commit 50c72d6989
25 changed files with 1402 additions and 805 deletions

View File

@ -1,11 +1,6 @@
<template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container">
<el-form-item label="站点选择">
<el-select v-model="form.siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据" clearable>
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="订阅topic">
<el-input
v-model="form.mqttTopic"
@ -90,12 +85,21 @@
<script>
import {deleteMqtt,getMqttList} from '@/api/ems/site'
import {getAllSites} from '@/api/ems/zddt'
import AddMqtt from './AddMqtt.vue'
export default {
name: "Mqtt",
components: {AddMqtt},
computed: { },
watch: {
'$route.query.siteId'(newSiteId) {
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
if (normalizedSiteId === this.form.siteId) {
return
}
this.form.siteId = normalizedSiteId
this.onSearch()
}
},
data() {
return {
form:{
@ -103,8 +107,6 @@ export default {
topicName:'',
mqttTopic:''
},
siteList:[],
searchLoading:false,
loading:false,
tableData:[],
pageSize:10,//分页栏当前每个数据总数
@ -113,6 +115,9 @@ export default {
}
},
methods:{
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
// 分页
handleSizeChange(val) {
this.pageSize = val;
@ -133,22 +138,19 @@ export default {
},
onReset(){
this.form={
siteId:'',
siteId:this.form.siteId,
topicName:'',
mqttTopic:''
}
this.pageNum =1//每次搜索从1开始搜索
this.getData()
},
//获取站点列表
getZdList(){
this.searchLoading=true
return getAllSites().then(response => {
this.siteList = response?.data || []
// if( this.siteList.length>0 ) this.siteId = this.siteList[0].siteId
}).finally(() => {this.searchLoading=false})
},
getData(){
if (!this.form.siteId) {
this.tableData = []
this.totalSize = 0
return
}
this.loading=true;
const {mqttTopic,topicName,siteId} = this.form;
const {pageNum,pageSize} = this;
@ -193,8 +195,7 @@ export default {
},
},
mounted() {
this.loading=true
this.getZdList()
this.form.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.getData()
}
}

View File

@ -19,23 +19,6 @@
</div>
<div class="main-content">
<el-form :inline="true" class="select-container">
<el-form-item label="站点">
<el-select
v-model="queryParams.siteId"
placeholder="请选择站点"
:loading="searchLoading"
loading-text="正在加载数据"
clearable
@change="handleSiteChange"
>
<el-option
v-for="(item, index) in siteList"
:key="index + 'siteSelect'"
:label="item.siteName"
:value="item.siteId"
/>
</el-select>
</el-form-item>
<el-form-item label="设备类型">
<el-select v-model="queryParams.deviceCategory" placeholder="请选择设备类型" clearable @change="onSearch">
<el-option
@ -75,16 +58,17 @@
<el-table class="common-table" :data="tableData" stripe max-height="560px">
<el-table-column prop="siteId" label="站点ID" min-width="120" />
<el-table-column label="设备类型/设备ID" min-width="180">
<template slot-scope="scope">
{{ (scope.row.deviceCategory || '-') + ' / ' + (scope.row.deviceId || '-') }}
</template>
</el-table-column>
<el-table-column prop="pointId" label="点位ID" min-width="280" />
<el-table-column label="点位名" min-width="140">
<template slot-scope="scope">
{{ scope.row.pointName || '-' }}
</template>
</el-table-column>
<el-table-column label="设备类型/设备ID" min-width="180">
<template slot-scope="scope">
{{ formatDeviceInfo(scope.row) }}
</template>
</el-table-column>
<el-table-column prop="dataKey" label="数据键" min-width="180" />
<el-table-column label="类型" width="88">
<template slot-scope="scope">
@ -100,7 +84,7 @@
<el-table-column prop="dataUnit" label="单位" min-width="80" />
<el-table-column label="操作" width="220" fixed="right">
<template slot-scope="scope">
<el-button type="text" :disabled="scope.row.pointType === 'calc'" @click="openCurveDialog(scope.row)">曲线</el-button>
<el-button type="text" @click="openCurveDialog(scope.row)">曲线</el-button>
<el-button type="text" @click="openEditDialog(scope.row.id)">编辑</el-button>
<el-button type="text" style="color: #f56c6c;" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
@ -132,14 +116,7 @@
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="站点" prop="siteId">
<el-select v-model="form.siteId" placeholder="请选择站点" @change="handleFormSiteChange">
<el-option
v-for="(item, index) in siteList"
:key="index + 'formSite'"
:label="item.siteName"
:value="item.siteId"
/>
</el-select>
<el-input v-model="form.siteId" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
@ -150,7 +127,21 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.pointType === 'data'">
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="点位ID" prop="pointId">
<el-input v-model.trim="form.pointId" placeholder="请输入点位ID系统唯一" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点位名称" prop="pointName">
<el-input v-model.trim="form.pointName" placeholder="请输入点位名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12" v-if="form.pointType === 'data' || form.pointType === 'calc'">
<el-form-item label="设备类型" prop="deviceCategory">
<el-select v-model="form.deviceCategory" placeholder="请选择设备类型" @change="handleFormCategoryChange">
<el-option
@ -162,11 +153,9 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12" v-if="form.pointType === 'data'">
<el-form-item label="设备ID" prop="deviceId">
<el-select v-model="form.deviceId" placeholder="请选择设备" clearable>
<el-select v-model="form.deviceId" placeholder="请选择设备">
<el-option
v-for="(item,index) in formDeviceList"
:key="item.__key || (index + 'formDevice')"
@ -176,18 +165,13 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12" v-if="form.pointType === 'data'">
<el-form-item label="寄存器地址" prop="registerAddress">
<el-input v-model.trim="form.registerAddress" placeholder="请输入寄存器地址" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="点位名称" prop="pointName">
<el-input v-model.trim="form.pointName" placeholder="请输入点位名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据键" prop="dataKey">
<el-input v-model.trim="form.dataKey" placeholder="请输入数据键" />
@ -229,8 +213,8 @@
<el-option
v-for="item in calcDataPointOptions"
:key="`${item.siteId || ''}_${item.deviceId || ''}_${item.dataKey}`"
:label="`${item.dataKey}${item.pointDesc ? ' - ' + item.pointDesc : ''}`"
:value="item.dataKey"
:label="`${item.pointId}${item.pointDesc ? ' - ' + item.pointDesc : ''}`"
:value="item.pointId"
/>
</el-select>
</el-form-item>
@ -343,7 +327,6 @@
<script>
import * as echarts from 'echarts'
import { getAllSites } from '@/api/ems/zddt'
import { getAllDeviceCategory } from '@/api/ems/search'
import {
importPointConfigCsv,
@ -354,12 +337,7 @@ import {
deletePointMatch,
getDeviceListBySiteAndCategory,
getPointConfigLatestValues,
getPointConfigCurve,
getPointCalcConfigList,
getPointCalcConfigDetail,
addPointCalcConfig,
updatePointCalcConfig,
deletePointCalcConfig
getPointConfigCurve
} from '@/api/ems/site'
export default {
@ -367,8 +345,6 @@ export default {
data() {
return {
loading: false,
searchLoading: false,
siteList: [],
deviceCategoryList: [],
tableData: [],
total: 0,
@ -379,6 +355,7 @@ export default {
pageSize: 10,
siteId: '',
deviceCategory: '',
deviceId: '',
dataKey: '',
pointDesc: '',
pointType: 'data'
@ -392,7 +369,8 @@ export default {
curveQuery: {
siteId: '',
deviceId: '',
dataKey: '',
pointId: '',
pointType: 'data',
rangeType: 'custom',
startTime: '',
endTime: ''
@ -412,6 +390,7 @@ export default {
deviceCategory: '',
deviceId: '',
registerAddress: '',
pointId: '',
pointName: '',
dataKey: '',
pointDesc: '',
@ -426,51 +405,9 @@ export default {
alarmUpper1: ''
},
rules: {
siteId: [{ required: true, message: '请选择站点', trigger: 'change' }],
siteId: [{ required: true, message: '请先在顶部选择站点', trigger: 'change' }],
pointType: [{ required: true, message: '请选择点位类型', trigger: 'change' }],
deviceCategory: [{
validator: (rule, value, callback) => {
if (this.form.pointType !== 'data') {
callback()
return
}
if (!String(value || '').trim()) {
callback(new Error('请选择设备类型'))
return
}
callback()
},
trigger: 'change'
}],
deviceId: [{
validator: (rule, value, callback) => {
if (this.form.pointType !== 'data') {
callback()
return
}
if (!String(value || '').trim()) {
callback(new Error('请选择设备ID'))
return
}
callback()
},
trigger: 'change'
}],
registerAddress: [{
validator: (rule, value, callback) => {
if (this.form.pointType !== 'data') {
callback()
return
}
if (!String(value || '').trim()) {
callback(new Error('请输入寄存器地址'))
return
}
callback()
},
trigger: 'blur'
}],
dataKey: [{ required: true, message: '请输入数据键', trigger: 'blur' }],
pointId: [{ required: true, message: '请输入点位ID', trigger: 'blur' }],
pointName: [{ required: true, message: '请输入点位名称', trigger: 'blur' }],
calcExpression: [{
validator: (rule, value, callback) => {
@ -489,18 +426,33 @@ export default {
}
}
},
watch: {
'$route.query.siteId'(newSiteId) {
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
if (normalizedSiteId === this.queryParams.siteId) {
return
}
this.queryParams.siteId = normalizedSiteId
this.queryParams.deviceCategory = ''
this.queryParams.deviceId = ''
this.onSearch()
}
},
methods: {
formatDeviceInfo(row) {
if (!row) {
return '-'
}
const deviceCategory = row.deviceCategory || ''
const deviceId = row.deviceId || ''
if (!deviceCategory && !deviceId) {
return '-'
}
return `${deviceCategory}/${deviceId}`
},
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
getSiteList() {
this.searchLoading = true
return getAllSites().then(response => {
this.siteList = response?.data || []
}).finally(() => {
this.searchLoading = false
})
},
getCategoryList() {
return getAllDeviceCategory().then(response => {
this.deviceCategoryList = response?.data || []
@ -535,40 +487,18 @@ export default {
},
getData() {
this.loading = true
if (this.activePointTab === 'calc') {
const calcParams = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
siteId: this.queryParams.siteId,
dataKey: this.queryParams.dataKey,
pointDesc: this.queryParams.pointDesc
}
getPointCalcConfigList(calcParams).then(response => {
const rows = response?.rows || []
this.tableData = rows.map(item => ({
...item,
pointType: 'calc',
latestValue: '-'
}))
this.total = response?.total || 0
this.loadLatestValues()
}).finally(() => {
this.loading = false
})
} else {
getPointMatchList(this.queryParams).then(response => {
const rows = response?.rows || []
this.tableData = rows.map(item => ({
...item,
pointType: item.pointType || 'data',
latestValue: '-'
}))
this.total = response?.total || 0
this.loadLatestValues()
}).finally(() => {
this.loading = false
})
}
getPointMatchList(this.queryParams).then(response => {
const rows = response?.rows || []
this.tableData = rows.map(item => ({
...item,
pointType: item.pointType || 'data',
latestValue: '-'
}))
this.total = response?.total || 0
this.loadLatestValues()
}).finally(() => {
this.loading = false
})
},
loadLatestValues() {
if (!this.tableData.length) {
@ -582,7 +512,55 @@ export default {
dataKey: item.dataKey
}))
if (!points.length) {
this.tableData = this.applyCalcLatestValues(this.tableData)
const calcRows = this.tableData.filter(item => item.pointType === 'calc')
if (!calcRows.length) {
this.tableData = this.applyCalcLatestValues(this.tableData)
return
}
this.loadCalcDependencyRows(calcRows).then(depRows => {
if (!depRows.length) {
this.tableData = this.applyCalcLatestValues(this.tableData)
return
}
const depPoints = depRows.map(item => ({
siteId: item.siteId,
deviceId: item.deviceId,
dataKey: item.dataKey
}))
return getPointConfigLatestValues({ points: depPoints }).then(response => {
const latestList = response?.data || []
const latestMap = latestList.reduce((acc, item) => {
const key = `${item.siteId || ''}__${item.deviceId || ''}__${item.dataKey || ''}`
acc[key] = item.pointValue
return acc
}, {})
const depRowsWithLatest = depRows.map(row => {
const key = `${row.siteId || ''}__${row.deviceId || ''}__${row.dataKey || ''}`
const latestValue = latestMap[key]
return {
...row,
latestValue: (latestValue === null || latestValue === undefined || latestValue === '') ? '-' : latestValue
}
})
const mergedRows = this.applyCalcLatestValues([...depRowsWithLatest, ...this.tableData])
const calcLatestMap = mergedRows
.filter(item => item.pointType === 'calc')
.reduce((acc, item) => {
acc[this.getCalcRowKey(item)] = item.latestValue
return acc
}, {})
this.tableData = this.tableData.map(row => {
if (row.pointType !== 'calc') return row
const nextLatest = calcLatestMap[this.getCalcRowKey(row)]
return {
...row,
latestValue: (nextLatest === null || nextLatest === undefined || nextLatest === '') ? '-' : nextLatest
}
})
})
}).catch(() => {
this.tableData = this.applyCalcLatestValues(this.tableData)
})
return
}
getPointConfigLatestValues({ points }).then(response => {
@ -603,6 +581,78 @@ export default {
this.tableData = this.applyCalcLatestValues(withDataLatestValue)
}).catch(() => {})
},
getCalcRowKey(row) {
return `${row.id || ''}__${row.siteId || ''}__${row.pointId || ''}`
},
extractExpressionTokens(expression) {
const expr = String(expression || '').trim()
if (!expr) return []
if (!/^[0-9A-Za-z_+\-*/().\s]+$/.test(expr)) return []
const matched = expr.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) || []
return Array.from(new Set(matched.map(item => String(item || '').trim()).filter(Boolean)))
},
loadCalcDependencyRows(calcRows = []) {
if (!calcRows.length) {
return Promise.resolve([])
}
const tokenBySite = {}
calcRows.forEach(row => {
const siteId = String(row.siteId || '').trim()
if (!siteId) return
const tokens = this.extractExpressionTokens(row.calcExpression)
if (!tokenBySite[siteId]) {
tokenBySite[siteId] = new Set()
}
tokens.forEach(token => tokenBySite[siteId].add(token))
})
const siteIds = Object.keys(tokenBySite)
if (!siteIds.length) {
return Promise.resolve([])
}
const tasks = siteIds.map(siteId => {
return getPointMatchList({
pageNum: 1,
pageSize: 5000,
siteId,
pointType: 'data'
}).then(response => {
const rows = response?.rows || []
const targetTokens = tokenBySite[siteId] || new Set()
if (!targetTokens.size) {
return []
}
const normalizedTokenSet = new Set()
targetTokens.forEach(token => {
normalizedTokenSet.add(token)
normalizedTokenSet.add(token.toUpperCase())
normalizedTokenSet.add(token.toLowerCase())
})
return rows.filter(item => {
const pointId = String(item.pointId || '').trim()
if (!pointId) return false
if (!item.siteId || !item.deviceId || !item.dataKey) return false
return normalizedTokenSet.has(pointId) ||
normalizedTokenSet.has(pointId.toUpperCase()) ||
normalizedTokenSet.has(pointId.toLowerCase())
}).map(item => ({
...item,
pointType: 'data',
latestValue: '-'
}))
}).catch(() => [])
})
return Promise.all(tasks).then(resultList => {
const flatRows = resultList.reduce((acc, rows) => acc.concat(rows || []), [])
const uniqueMap = {}
flatRows.forEach(row => {
const key = `${row.siteId || ''}__${row.deviceId || ''}__${row.dataKey || ''}`
if (!uniqueMap[key]) {
uniqueMap[key] = row
}
})
return Object.values(uniqueMap)
})
},
applyCalcLatestValues(rows = []) {
const dataPointMap = {}
rows.forEach(row => {
@ -610,12 +660,31 @@ export default {
if (row.latestValue === '-' || row.latestValue === '' || row.latestValue === null || row.latestValue === undefined) {
return
}
const deviceKey = `${row.siteId || ''}__${row.deviceId || ''}__${row.dataKey || ''}`
const siteKey = `${row.siteId || ''}__${row.dataKey || ''}`
dataPointMap[deviceKey] = row.latestValue
if (dataPointMap[siteKey] === undefined) {
dataPointMap[siteKey] = row.latestValue
}
const tokens = [row.pointId]
.map(item => String(item || '').trim())
.filter(Boolean)
tokens.forEach(token => {
const deviceKey = `${row.siteId || ''}__${row.deviceId || ''}__${token}`
const siteKey = `${row.siteId || ''}__${token}`
dataPointMap[deviceKey] = row.latestValue
if (dataPointMap[siteKey] === undefined) {
dataPointMap[siteKey] = row.latestValue
}
const upperToken = token.toUpperCase()
const lowerToken = token.toLowerCase()
const upperDeviceKey = `${row.siteId || ''}__${row.deviceId || ''}__${upperToken}`
const upperSiteKey = `${row.siteId || ''}__${upperToken}`
const lowerDeviceKey = `${row.siteId || ''}__${row.deviceId || ''}__${lowerToken}`
const lowerSiteKey = `${row.siteId || ''}__${lowerToken}`
dataPointMap[upperDeviceKey] = row.latestValue
if (dataPointMap[upperSiteKey] === undefined) {
dataPointMap[upperSiteKey] = row.latestValue
}
dataPointMap[lowerDeviceKey] = row.latestValue
if (dataPointMap[lowerSiteKey] === undefined) {
dataPointMap[lowerSiteKey] = row.latestValue
}
})
})
return rows.map(row => {
if (row.pointType !== 'calc') return row
@ -632,9 +701,17 @@ export default {
const tokenPattern = /\b[A-Za-z_][A-Za-z0-9_]*\b/g
const missingKeys = []
const resolvedExpr = expr.replace(tokenPattern, token => {
const deviceKey = `${siteId || ''}__${deviceId || ''}__${token}`
const siteKey = `${siteId || ''}__${token}`
const value = dataPointMap[deviceKey] !== undefined ? dataPointMap[deviceKey] : dataPointMap[siteKey]
const candidates = [token, token.toUpperCase(), token.toLowerCase()]
let value
for (let i = 0; i < candidates.length; i++) {
const candidate = candidates[i]
const deviceKey = `${siteId || ''}__${deviceId || ''}__${candidate}`
const siteKey = `${siteId || ''}__${candidate}`
value = dataPointMap[deviceKey] !== undefined ? dataPointMap[deviceKey] : dataPointMap[siteKey]
if (value !== undefined && value !== null && value !== '' && value !== '-') {
break
}
}
if (value === undefined || value === null || value === '' || value === '-') {
missingKeys.push(token)
return 'NaN'
@ -664,11 +741,13 @@ export default {
this.getData()
},
onReset() {
const currentSiteId = this.queryParams.siteId
this.queryParams = {
pageNum: 1,
pageSize: 10,
siteId: '',
siteId: currentSiteId,
deviceCategory: '',
deviceId: '',
dataKey: '',
pointDesc: '',
pointType: this.activePointTab
@ -684,10 +763,6 @@ export default {
this.queryParams.pageNum = 1
this.getData()
},
handleSiteChange() {
this.queryParams.deviceCategory = ''
this.onSearch()
},
handleSizeChange(val) {
this.queryParams.pageSize = val
this.getData()
@ -748,6 +823,7 @@ export default {
deviceCategory: this.queryParams.deviceCategory || '',
deviceId: '',
registerAddress: '',
pointId: '',
pointName: '',
dataKey: '',
pointDesc: '',
@ -780,26 +856,18 @@ export default {
openEditDialog(id) {
this.resetForm()
this.loading = true
if (this.activePointTab === 'calc') {
getPointCalcConfigDetail(id).then(response => {
this.form = Object.assign(this.form, response?.data || {})
this.form.pointType = 'calc'
this.dialogVisible = true
this.loadCalcDataPointOptions()
}).finally(() => {
this.loading = false
})
} else {
getPointMatchDetail(id).then(response => {
this.form = Object.assign(this.form, response?.data || {})
this.form.pointType = this.form.pointType || 'data'
this.form.isAlarm = Number(this.form.isAlarm || 0)
this.dialogVisible = true
return this.getFormDeviceList()
}).finally(() => {
this.loading = false
})
}
getPointMatchDetail(id).then(response => {
this.form = Object.assign(this.form, response?.data || {})
this.form.pointType = this.form.pointType || 'data'
this.form.isAlarm = Number(this.form.isAlarm || 0)
this.dialogVisible = true
if (this.form.pointType === 'calc') {
return this.loadCalcDataPointOptions()
}
return this.getFormDeviceList()
}).finally(() => {
this.loading = false
})
},
handleDelete(id) {
this.$confirm('确认删除该点位配置吗?', '提示', {
@ -807,22 +875,12 @@ export default {
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (this.activePointTab === 'calc') {
return deletePointCalcConfig(id)
} else {
return deletePointMatch(id)
}
return deletePointMatch(id)
}).then(() => {
this.$message.success('删除成功')
this.getData()
}).catch(() => {})
},
handleFormSiteChange() {
this.form.deviceCategory = ''
this.form.deviceId = ''
this.formDeviceList = []
this.loadCalcDataPointOptions()
},
handleFormCategoryChange() {
this.form.deviceId = ''
this.getFormDeviceList()
@ -856,7 +914,7 @@ export default {
params.deviceId = this.form.deviceId
}
return getPointMatchList(params).then(response => {
this.calcDataPointOptions = response?.rows || []
this.calcDataPointOptions = (response?.rows || []).filter(item => String(item.pointId || '').trim())
}).catch(() => {
this.calcDataPointOptions = []
})
@ -871,15 +929,12 @@ export default {
this.selectedCalcDataKey = ''
},
openCurveDialog(row) {
if (row.pointType === 'calc') {
this.$message.warning('计算点暂不支持曲线查询')
return
}
this.curveDialogTitle = `曲线 - ${row.pointDesc || row.dataKey || ''}`
this.curveQuery = {
siteId: row.siteId || '',
deviceId: row.deviceId || '',
dataKey: row.dataKey || '',
pointId: row.pointId || '',
pointType: row.pointType || 'data',
rangeType: 'custom',
startTime: '',
endTime: ''
@ -952,7 +1007,7 @@ export default {
return normalized.slice(0, 16)
},
loadCurveData() {
if (!this.curveQuery.siteId || !this.curveQuery.deviceId || !this.curveQuery.dataKey) {
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
this.$message.warning('点位信息不完整,无法查询曲线')
return
}
@ -964,7 +1019,16 @@ export default {
this.curveQuery.startTime = this.curveCustomRange[0]
this.curveQuery.endTime = this.curveCustomRange[1]
this.curveLoading = true
getPointConfigCurve(this.curveQuery).then(response => {
const query = {
siteId: this.curveQuery.siteId,
deviceId: this.curveQuery.deviceId,
pointId: this.curveQuery.pointId,
pointType: this.curveQuery.pointType,
rangeType: this.curveQuery.rangeType,
startTime: this.curveQuery.startTime,
endTime: this.curveQuery.endTime
}
getPointConfigCurve(query).then(response => {
const rows = response?.data || []
this.renderCurveChart(rows)
}).catch(() => {
@ -1016,10 +1080,38 @@ export default {
],
series: [
{
name: this.curveQuery.dataKey || '点位值',
name: this.curveQuery.pointId || '点位值',
type: 'line',
data: yData,
connectNulls: true
connectNulls: true,
markPoint: {
symbolSize: 44,
label: {
show: true,
position: 'top',
formatter(params) {
const name = params && params.name ? params.name : ''
const value = params && params.value !== undefined && params.value !== null ? params.value : '-'
return `${name}: ${value}`
}
},
data: [
{
type: 'max',
name: '最大值',
itemStyle: {
color: '#f56c6c'
}
},
{
type: 'min',
name: '最小值',
itemStyle: {
color: '#409eff'
}
}
]
}
}
]
})
@ -1039,27 +1131,18 @@ export default {
? ''
: String(this.form.deviceId).trim()
const dataKey = (this.form.dataKey || '').trim()
if (!dataKey) {
this.$message.warning('请输入数据键')
const pointId = (this.form.pointId || '').trim()
if (!pointId) {
this.$message.warning('请输入点位ID')
return
}
const normalizedDeviceCategory = String(this.form.deviceCategory || '').trim()
if (pointType === 'data') {
if (!this.form.deviceCategory) {
this.$message.warning('请选择设备类型')
return
}
if (!deviceId) {
this.$message.warning('请选择设备ID')
return
}
if (!this.form.registerAddress || !String(this.form.registerAddress).trim()) {
this.$message.warning('请输入寄存器地址')
return
}
if (this.formDeviceList.length > 0 && !this.formDeviceList.some(item => item.__value === deviceId)) {
if (deviceId && this.formDeviceList.length > 0 && !this.formDeviceList.some(item => item.__value === deviceId)) {
this.$message.warning('设备ID无效请重新选择')
return
}
this.form.registerAddress = this.form.registerAddress ? String(this.form.registerAddress).trim() : ''
} else {
const calcExpression = String(this.form.calcExpression || '').trim()
if (!calcExpression) {
@ -1070,19 +1153,24 @@ export default {
this.$message.warning('计算表达式格式不正确')
return
}
this.form.deviceCategory = this.form.deviceCategory || ''
this.form.deviceId = deviceId
this.form.registerAddress = this.form.registerAddress || ''
}
this.form.deviceCategory = normalizedDeviceCategory
this.form.deviceId = deviceId
this.form.pointId = pointId
this.form.dataKey = dataKey
this.form.pointType = pointType
if (pointType === 'calc') {
const request = this.form.id ? updatePointCalcConfig : addPointCalcConfig
const request = this.form.id ? updatePointMatch : addPointMatch
const calcData = {
id: this.form.id,
siteId: this.form.siteId,
pointType: 'calc',
deviceCategory: this.form.deviceCategory,
deviceId: '',
registerAddress: '',
pointId: this.form.pointId,
pointName: this.form.pointName,
dataKey: this.form.dataKey,
pointDesc: this.form.pointDesc,
@ -1111,7 +1199,8 @@ export default {
}
},
mounted() {
Promise.all([this.getSiteList(), this.getCategoryList()]).then(() => {
this.queryParams.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.getCategoryList().then(() => {
this.getData()
})
},

View File

@ -1,11 +1,6 @@
<template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container">
<el-form-item label="站点选择">
<el-select v-model="siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据" @change="onSearch" clearable>
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="年份选择">
<el-date-picker
v-model="defaultYear"
@ -33,7 +28,7 @@
:key="item.id"
>
<div slot="header" class="time-range-header">
<span class="card-title">{{siteList.find(i=>i.siteId===item.siteId).siteName || item.siteId || ''}}-{{item.month}}月电价时段划分</span>
<span class="card-title">{{item.siteId || ''}}-{{item.month}}月电价时段划分</span>
<div>
<el-button type="primary" size="mini" @click="addPowerConfig(item.id)">编辑</el-button>
<el-button type="warning" size="mini" @click="deletePowerConfig(item)">删除</el-button>
@ -61,21 +56,28 @@
<script>
import {energyPriceConfig,listPriceConfig} from '@/api/ems/powerTariff'
import {getAllSites} from '@/api/ems/zddt'
import AddPowerTariff from './AddPowerTariff.vue'
import DateTimeSelect from "@/views/ems/search/DateTimeSelect.vue";
export default {
name: "PowerTariff",
components: {DateTimeSelect, AddPowerTariff},
computed: { },
watch: {
'$route.query.siteId'(newSiteId) {
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
if (normalizedSiteId === this.siteId) {
return
}
this.siteId = normalizedSiteId
this.onSearch()
}
},
data() {
return {
loading:false,
pageNum:1,
pageSize:40,
searchLoading:false,
siteId:'',
siteList:[],
tableData:[],
tableTotal:0,
defaultYear:'',
@ -93,6 +95,9 @@ export default {
}
},
methods:{
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
resetTableData(){
this.tableData=[]
this.tableTotal=0
@ -102,19 +107,16 @@ export default {
onSearch(){
this.getData(true)
},
//获取站点列表
getZdList(){
this.searchLoading=true
return getAllSites().then(response => {
this.siteList = response?.data || []
if( this.siteList.length>0 ) this.siteId = this.siteList[0].siteId
}).finally(() => {this.searchLoading=false})
},
changeDefaultYear(){
this.getData(true)
},
getData(reset=false){
reset && this.resetTableData()
if (!this.siteId) {
this.tableData = []
this.tableTotal = 0
return
}
if(!reset && this.tableData.length>=this.tableTotal) return
this.loading=true;
const date = new Date(this.defaultYear).getFullYear()
@ -163,10 +165,8 @@ export default {
},
mounted() {
this.defaultYear = new Date()
this.loading=true
this.getZdList().then(()=>{
this.getData(true)
})
this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.getData(true)
}
}
</script>

View File

@ -17,19 +17,11 @@
label-width="140px"
>
<el-form-item label="站点" prop="siteId">
<el-select
<el-input
v-model="formData.siteId"
placeholder="请选择"
:style="{ width: '50%' }"
@change="changeType"
>
<el-option
:label="item.siteName"
:value="item.siteId"
v-for="(item, index) in siteList"
:key="index + 'siteOptions'"
></el-option>
</el-select>
disabled
></el-input>
</el-form-item>
<el-form-item label="设备保护名称" prop="faultName">
<el-input
@ -272,7 +264,6 @@
</template>
<script>
import {mapState} from "vuex";
import {getAllSites} from "@/api/ems/zddt";
import {validText} from "@/utils/validate";
import {addProtectPlan, getDeviceListBySiteAndCategory, getProtectPlan, updateProtectPlan} from "@/api/ems/site";
import {getAllDeviceCategory, pointFuzzyQuery} from "@/api/ems/search";
@ -301,7 +292,6 @@ export default {
protectionSettings: [],
protectionPlan: [],
dialogTableVisible: false,
siteList: [], //
formData: {
id: "", //
siteId: "", //ID
@ -313,13 +303,6 @@ export default {
description: '',//
},
rules: {
siteId: [
{
required: true,
message: "请选择站点",
trigger: ["blur", "change"],
},
],
faultName: [
{required: true, message: "请输入设备保护名称", trigger: "blur"},
],
@ -353,8 +336,8 @@ export default {
},
methods: {
open(id, siteId) {
this.formData.siteId = siteId || ''
this.dialogTableVisible = true
this.getZdList();
this.getDeviceCategoryList().then(() => {
if (id && siteId) {
// this.getDeviceList('PCS', siteId)
@ -560,56 +543,6 @@ export default {
console.log('设置选中设备类型、设备配置完成', this[type][index])
},
//
getZdList() {
this.loading += 1;
getAllSites()
.then((response) => {
this.siteList = response?.data || [];
})
.finally(() => {
this.loading -= 1;
});
},
//
//
//
changeType() {
//pcsbms
this.updateSiteDeviceList()
if (this.protectionSettings.length > 0) {
const list = this.protectionSettings
list.forEach((item) => {
item.point = ""
item.pointName = ""
item.deviceId = []
item.deviceName = []
item.categoryName = ''
item.deviceCategory = ''
});
// this.$set(this,'protectionSettings',list)
this.$nextTick(() => {
this.protectionSettings.splice(0, this.protectionSettings.length, ...list)
})
}
if (this.protectionPlan.length > 0) {
const list = this.protectionPlan
list.forEach((item) => {
item.point = ""
item.pointName = ""
item.deviceId = []
item.deviceName = []
item.categoryName = ''
item.deviceCategory = ''
});
// this.$set(this,'protectionPlan',list)
this.$nextTick(() => {
this.protectionPlan.splice(0, this.protectionPlan.length, ...list)
})
}
},
saveDialog() {
function getToastMsg(name, type, index) {
return {

View File

@ -5,22 +5,6 @@
v-loading="loading"
>
<el-form :inline="true" class="select-container">
<el-form-item label="站点选择">
<el-select
v-model="form.siteId"
placeholder="请选择换电站名称"
:loading="searchLoading"
loading-text="正在加载数据"
clearable
>
<el-option
:label="item.siteName"
:value="item.siteId"
v-for="(item, index) in siteList"
:key="index + 'zdxeSelect'"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="故障名称">
<el-input
v-model="form.faultName"
@ -34,8 +18,8 @@
<el-button @click="onReset" native-type="button">重置</el-button>
</el-form-item>
</el-form>
<el-button type="primary" @click="addDevice" native-type="button"
>新增设备</el-button
<el-button type="primary" @click="addPlan" native-type="button"
>新增方案</el-button
>
<el-table
class="common-table"
@ -92,8 +76,8 @@
style="margin-top: 15px; text-align: center"
>
</el-pagination>
<add-device
ref="addDevice"
<add-plan
ref="addPlan"
@update="getData"
/>
</div>
@ -104,20 +88,18 @@ import {
protectPlanList,
deleteProtectPlan,
} from "@/api/ems/site";
import { getAllSites } from "@/api/ems/zddt";
import AddDevice from "./AddDevice.vue";
import getQuerySiteId from '@/mixins/ems/getQuerySiteId'
import AddPlan from "./AddPlan.vue";
export default {
name: "SBBH",
components: { AddDevice },
components: { AddPlan },
mixins: [getQuerySiteId],
data() {
return {
form:{
siteId:'',
faultName:''
},
loading: false,
searchLoading: false,
siteList: [],
tableData: [],
pageSize: 10, //分页栏当前每个数据总数
pageNum: 1, //分页栏当前页数
@ -126,6 +108,10 @@ export default {
};
},
methods: {
init() {
this.pageNum = 1;
this.getData();
},
handleProtectionSettings(data){
if(!data || !JSON.parse(data)) return
const arr = JSON.parse(data),
@ -144,13 +130,17 @@ export default {
})
return str.join('')
},
// 新增设备 展示弹窗
addDevice() {
this.$refs.addDevice.open()
// 新增方案 展示弹窗
addPlan() {
if (!this.siteId) {
this.$message.warning('请先在顶部选择站点')
return
}
this.$refs.addPlan.open('', this.siteId)
},
// 编辑设备
editDevice(row) {
this.$refs.addDevice.open(row.id,row.siteId)
this.$refs.addPlan.open(row.id, this.siteId || row.siteId)
},
//删除设备
deleteDevice(row) {
@ -210,7 +200,6 @@ export default {
// 重置
onReset() {
this.form={
siteId: "",
faultName: "",
}
this.pageNum = 1; //每次搜索从1开始搜索
@ -218,9 +207,14 @@ export default {
},
// 获取数据
getData() {
if (!this.siteId) {
this.tableData = []
this.totalSize = 0
return
}
this.loading = true;
const { pageNum, pageSize } = this,{siteId,faultName=''}=this.form;
protectPlanList({ siteId, faultName,pageNum, pageSize })
const { pageNum, pageSize } = this,{faultName=''}=this.form;
protectPlanList({ siteId: this.siteId, faultName,pageNum, pageSize })
.then((response) => {
this.tableData = response?.rows || [];
this.totalSize = response?.total || 0;
@ -229,29 +223,6 @@ export default {
this.loading = false;
});
},
//获取站点列表
getZdList() {
this.searchLoading = true;
return getAllSites()
.then((response) => {
this.siteList = response?.data || [];
if (this.siteList.length > 0) this.form.siteId = this.siteList[0].siteId;
})
.finally(() => {
this.searchLoading = false;
});
},
},
mounted() {
this.loading = true;
this.form = {
siteId: "",
faultName: "",
};
this.pageNum = 1; //每次搜索从1开始搜索
this.getZdList().then(() => {
this.getData();
});
},
};
</script>

View File

@ -3,133 +3,135 @@
:show-close="false" destroy-on-close lock-scroll append-to-body width="800px" class="ems-dialog"
:title="mode === 'add'?'新增设备':`编辑设备` ">
<div v-loading="loading>0">
<el-form v-loading="loading>0" ref="addTempForm" inline :model="formData" :rules="rules" size="medium"
label-width="120px" class="device-form">
<el-form-item label="站点" prop="siteId">
<el-select v-model="formData.siteId" placeholder="请选择" :style="{width: '100%'}" @change="changeType">
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList"
:key="index+'siteOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备id" prop="deviceId">
<el-input v-model="formData.deviceId" placeholder="请输入" maxlength="60" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="formData.deviceName" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="设备描述" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入" clearable
:style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="工作状态" prop="communicationStatus">
<el-select v-model="formData.communicationStatus" placeholder="请选择" :style="{width: '100%'}">
<el-option :label="value" :value="key" v-for="(value,key) in communicationStatusOptions"
:key="key+'communicationStatusOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="formData.deviceType" placeholder="请选择" :style="{width: '100%'}">
<el-option :label="value" :value="key" v-for="(value,key) in deviceTypeOptions"
:key="key+'deviceTypeOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类别" prop="deviceCategory">
<el-select v-model="formData.deviceCategory" placeholder="请选择" :style="{width: '100%'}"
@change="changeType">
<el-option :label="item.name" :value="item.code" v-for="(item,index) in deviceCategoryList"
:key="index+'deviceCategoryList'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="上级设备" prop="parentId" v-if="dccDeviceCategoryList.includes(formData.deviceCategory)">
<el-select v-model="formData.parentId"
:placeholder="parentDeviceList.length === 0 && !formData.siteId ? '请先选择站点' : '请选择'"
:style="{width: '100%'}">
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in parentDeviceList"
:key="index+'parentDeviceList'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="TCP设备的ip地址" prop="ipAddress" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.ipAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="TCP设备的端口号" prop="ipPort" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.ipPort" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="从站地址" prop="slaveId" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.slaveId" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="串口路径" prop="serialPort">
<el-input v-model="formData.serialPort" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="波特率" prop="baudRate">
<el-input v-model="formData.baudRate" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="数据位" prop="dataBits">
<el-input v-model="formData.dataBits" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="停止位" prop="stopBits">
<el-input v-model="formData.stopBits" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="校验位" prop="parity">
<el-input v-model="formData.parity" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="图片" prop="pictureUrl">
<image-upload :limit="1" :drag="false" @input="uploadImage" :value="formData.pictureUrl"/>
</el-form-item>
</el-form>
<!-- pcs配置-->
<el-form v-if="isPcs" ref="pcsSettingForm" inline :model="pcsSetting" size="medium"
label-width="120px" class="device-form" :rules="pcsSettingRules">
<div style="font-size: 14px;padding: 10px 0 20px;font-weight: 600;">PCS配置</div>
<el-form-item label="开关机地址" prop="pointAddress">
<el-input v-model="pcsSetting.pointAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="功率地址" prop="powerAddress">
<el-input v-model="pcsSetting.powerAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="开机指令" prop="startCommand">
<el-input v-model="pcsSetting.startCommand" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="开机目标功率" prop="startPower">
<el-input v-model="pcsSetting.startPower" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="关机指令" prop="stopCommand">
<el-input v-model="pcsSetting.stopCommand" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="关机目标功率" prop="stopPower">
<el-input v-model="pcsSetting.stopPower" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="电池簇数" prop="clusterNum">
<el-input v-model="pcsSetting.clusterNum" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<br>
<template v-for="index in parseInt(pcsSetting.clusterNum) || 0">
<el-form-item :label="'电池簇'+(index)+'地址'"
prop="clusterPointAddress">
<el-input v-model="pcsSetting.clusterPointAddress[index-1]" placeholder="请输入" clearable
<div class="form-layout" :class="{ 'has-pcs': isPcs }">
<el-form v-loading="loading>0" ref="addTempForm" inline :model="formData" :rules="rules" size="medium"
label-width="120px" class="device-form base-form">
<el-form-item label="站点" prop="siteId">
<el-select v-model="formData.siteId" placeholder="请选择" :style="{width: '100%'}" @change="changeType">
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList"
:key="index+'siteOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备id" prop="deviceId">
<el-input v-model="formData.deviceId" placeholder="请输入" maxlength="60" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="formData.deviceName" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="设备描述" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入" clearable
:style="{width: '100%'}">
</el-input>
</el-form-item>
</template>
</el-form>
<el-form-item label="工作状态" prop="communicationStatus">
<el-select v-model="formData.communicationStatus" placeholder="请选择" :style="{width: '100%'}">
<el-option :label="value" :value="key" v-for="(value,key) in communicationStatusOptions"
:key="key+'communicationStatusOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型" prop="deviceType">
<el-select v-model="formData.deviceType" placeholder="请选择" :style="{width: '100%'}">
<el-option :label="value" :value="key" v-for="(value,key) in deviceTypeOptions"
:key="key+'deviceTypeOptions'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类别" prop="deviceCategory">
<el-select v-model="formData.deviceCategory" placeholder="请选择" :style="{width: '100%'}"
@change="changeType">
<el-option :label="item.name" :value="item.code" v-for="(item,index) in deviceCategoryList"
:key="index+'deviceCategoryList'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="上级设备" prop="parentId" v-if="dccDeviceCategoryList.includes(formData.deviceCategory)">
<el-select v-model="formData.parentId"
:placeholder="parentDeviceList.length === 0 && !formData.siteId ? '请先选择站点' : '请选择'"
:style="{width: '100%'}">
<el-option :label="item.deviceName" :value="item.id" v-for="(item,index) in parentDeviceList"
:key="index+'parentDeviceList'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="TCP设备的ip地址" prop="ipAddress" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.ipAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="TCP设备的端口号" prop="ipPort" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.ipPort" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="从站地址" prop="slaveId" v-if="formData.deviceType === 'TCP'">
<el-input v-model="formData.slaveId" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="串口路径" prop="serialPort">
<el-input v-model="formData.serialPort" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="波特率" prop="baudRate">
<el-input v-model="formData.baudRate" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="数据位" prop="dataBits">
<el-input v-model="formData.dataBits" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="停止位" prop="stopBits">
<el-input v-model="formData.stopBits" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="校验位" prop="parity">
<el-input v-model="formData.parity" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
</el-form>
<!-- pcs配置-->
<el-form v-if="isPcs" ref="pcsSettingForm" inline :model="pcsSetting" size="medium"
label-width="120px" class="device-form pcs-form" :rules="pcsSettingRules">
<div style="font-size: 14px;padding: 10px 0 20px;font-weight: 600;">PCS配置</div>
<el-form-item label="开关机地址" prop="pointAddress">
<el-input v-model="pcsSetting.pointAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="功率地址" prop="powerAddress">
<el-input v-model="pcsSetting.powerAddress" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="开机指令" prop="startCommand">
<el-input v-model="pcsSetting.startCommand" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="开机目标功率" prop="startPower">
<el-input v-model="pcsSetting.startPower" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="关机指令" prop="stopCommand">
<el-input v-model="pcsSetting.stopCommand" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="关机目标功率" prop="stopPower">
<el-input v-model="pcsSetting.stopPower" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="倍率" prop="powerMultiplier">
<el-input v-model="pcsSetting.powerMultiplier" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<el-form-item label="电池簇数" prop="clusterNum">
<el-input v-model="pcsSetting.clusterNum" placeholder="请输入" clearable :style="{width: '100%'}">
</el-input>
</el-form-item>
<br>
<template v-for="index in parseInt(pcsSetting.clusterNum) || 0">
<el-form-item :label="'电池簇'+(index)+'地址'"
prop="clusterPointAddress">
<el-input v-model="pcsSetting.clusterPointAddress[index-1]" placeholder="请输入" clearable
:style="{width: '100%'}">
</el-input>
</el-form-item>
</template>
</el-form>
</div>
</div>
<div slot="footer">
<el-button @click="closeDialog">取消</el-button>
@ -177,6 +179,13 @@ export default {
callback();
}
}
const validateDecimal = (rule, value, callback) => {
if (value !== '' && !/^(0|[1-9]\d*)(\.\d+)?$/.test(value)) {
callback(new Error('只能输入非负数字!'));
} else {
callback();
}
}
return {
loading: 0,
dccDeviceCategoryList: ['CLUSTER', 'BATTERY'],//需要展示上级设备的设备类型
@ -201,7 +210,6 @@ export default {
dataBits: '',//数据位
stopBits: '',//停止位
parity: '',//校验位
pictureUrl: '',//设备图片
slaveId: '',//从站地址
},
pcsSetting: {
@ -212,6 +220,7 @@ export default {
stopCommand: "",//关机指令
startPower: '',//开机目标功率
stopPower: '',//关机目标功率
powerMultiplier: '',//目标功率倍率
clusterNum: '',//电池簇数
clusterPointAddress: []//电池簇地址
},
@ -264,9 +273,6 @@ export default {
parity: [
{validator: validateText, trigger: 'blur'}
],
// pictureUrl:[
// { required: true, message: '请上传图片', trigger: ['blur', 'change']}
// ],
},
pcsSettingRules: {
pointAddress: [
@ -290,6 +296,9 @@ export default {
stopPower: [
{validator: validateText, trigger: 'blur'}
],
powerMultiplier: [
{validator: validateDecimal, trigger: 'blur'}
],
clusterNum: [
{required: true, message: '请输入电池簇数', trigger: 'blur'},
{validator: validateNumber, trigger: 'blur'}
@ -351,9 +360,6 @@ export default {
this.getParentDeviceList()
}
},
uploadImage(data) {
this.formData.pictureUrl = data
},
//获取站点列表
getZdList() {
this.loading += 1
@ -404,7 +410,6 @@ export default {
dataBits = '',//数据位
stopBits = '',//停止位
parity = '',//校验位
pictureUrl = '',//设备图片
slaveId = '',//从站地址
} = this.formData;
const {
@ -415,6 +420,7 @@ export default {
stopCommand,
startPower,
stopPower,
powerMultiplier,
clusterNum,
clusterPointAddress
} = this.pcsSetting
@ -434,7 +440,6 @@ export default {
dataBits,
stopBits,
parity,
pictureUrl,
slaveId,
}
if (this.isPcs) {
@ -445,6 +450,7 @@ export default {
stopCommand,
startPower,
stopPower,
powerMultiplier,
clusterNum,
clusterPointAddress: JSON.stringify(!clusterNum ? [] : (clusterPointAddress || []).slice(0, clusterNum))
}
@ -508,7 +514,6 @@ export default {
dataBits: '',//数据位
stopBits: '',//停止位
parity: '',//校验位
pictureUrl: '',//设备图片
slaveId: '',//从站地址
}
this.pcsSetting = {
@ -519,6 +524,7 @@ export default {
stopCommand: "",//关机指令
startPower: '',//开机目标功率
stopPower: '',//关机目标功率
powerMultiplier: '',//目标功率倍率
clusterNum: '',//电池簇数
clusterPointAddress: []//电池簇地址
}
@ -531,6 +537,25 @@ export default {
</script>
<style scoped lang="scss">
.form-layout {
display: block;
}
.form-layout.has-pcs {
display: flex;
align-items: flex-start;
gap: 16px;
}
.base-form {
width: 100%;
}
.form-layout.has-pcs .base-form,
.form-layout.has-pcs .pcs-form {
width: calc(50% - 8px);
}
.device-form {
::v-deep .el-form-item--medium .el-form-item__content {
width: 260px;
@ -541,4 +566,8 @@ export default {
margin-right: 0;
}
}
.form-layout.has-pcs .device-form .el-form-item {
width: 100%;
}
</style>

View File

@ -1,13 +1,6 @@
<template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container">
<el-form-item label="站点选择">
<el-select v-model="siteId" placeholder="请选择换电站名称" :loading="searchLoading" loading-text="正在加载数据"
@change="onSearch" clearable>
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList"
:key="index+'zdxeSelect'"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备类型">
<el-select v-model="deviceCategory" placeholder="请选择设备类型" @change="onSearch" clearable>
<el-option
@ -24,34 +17,6 @@
</el-form-item>
</el-form>
<el-button type="primary" @click="addDevice" native-type="button">新增设备</el-button>
<el-dropdown @command="(val)=>downloadPointDetail(val,false)">
<el-button
style="margin-left:10px;"
type="primary"
plain>
下载点位清单
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item,index) in deviceCategoryList" :key="index+'deviceCategoryList'"
:command="item">
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- <el-dropdown @command="(val)=>uploadPointDetail(val,false)">-->
<!-- <el-button-->
<!-- style="margin-left:10px;"-->
<!-- type="success"-->
<!-- plain>-->
<!-- 上传点位清单-->
<!-- </el-button>-->
<!-- <el-dropdown-menu slot="dropdown">-->
<!-- <el-dropdown-item v-for="(item,index) in deviceCategoryList" :key="index+'deviceCategoryList'"-->
<!-- :command="item">-->
<!-- {{ item.name }}-->
<!-- </el-dropdown-item>-->
<!-- </el-dropdown-menu>-->
<!-- </el-dropdown>-->
<el-table
class="common-table"
:data="tableData"
@ -79,39 +44,11 @@
prop="categoryName"
label="设备类别">
</el-table-column>
<el-table-column
prop="deviceStatus"
label="在线状态">
<template slot-scope="scope">
<span>{{ $store.state.ems.deviceStatusOptions[scope.row.deviceStatus] }}</span>
<!-- <pcs-switch v-if="scope.row.deviceCategory === 'PCS' && ![null,'',undefined].includes(scope.row.deviceStatus)"-->
<!-- style="margin-left:5px;"-->
<!-- :data="{siteId:scope.row.siteId,deviceStatus:scope.row.deviceStatus,deviceId:scope.row.deviceId,deviceName:scope.row.deviceName}"-->
<!-- @updateSuccess="getData"/>-->
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="250">
width="180">
<template slot-scope="scope">
<el-button
@click="downloadPointDetail(scope.row,true)"
style="margin-top:10px;"
type="primary"
plain
size="mini">
下载点位清单
</el-button>
<el-button
@click="uploadPointDetail(scope.row,true)"
style="margin-top:10px;"
type="success"
plain
size="mini">
上传点位清单
</el-button>
<br>
<el-button
@click="editDevice(scope.row)"
style="margin-top:10px;"
@ -154,30 +91,34 @@
</div>
</el-dialog>
<add-device ref="addDevice" :mode="mode" :id="editDeviceId" @update="getData" @clear="clearEditDeviceData"/>
<point-upload ref="pointUpload" @update="getData"/>
</div>
</template>
<script>
import {deleteService, getDeviceDetailInfo, getDeviceInfoList} from '@/api/ems/site'
import {getAllSites} from '@/api/ems/zddt'
import {formatNumber} from "@/filters/ems";
import {getAllDeviceCategory} from '@/api/ems/search'
import AddDevice from "./AddDevice.vue";
import PointUpload from "./PointUpload.vue";
// import PcsSwitch from "./PcsSwitch.vue";
export default {
name: "Sblb",
components: {AddDevice, PointUpload},
components: {AddDevice},
watch: {
'$route.query.siteId'(newSiteId) {
const normalizedSiteId = this.hasValidSiteId(newSiteId) ? String(newSiteId).trim() : ''
if (normalizedSiteId === this.siteId) {
return
}
this.siteId = normalizedSiteId
this.onSearch()
}
},
data() {
return {
loading: false,
searchLoading: false,
mode: '',//新增、编辑设备
editDeviceId: '',//编辑设备id
siteId: '',
siteList: [],
deviceCategory: '',//搜索栏设备类型
deviceCategoryList: [],//设备类别
tableData: [],
@ -209,34 +150,15 @@ export default {
}
},
methods: {
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
// 获取设备类别
getDeviceCategoryList() {
getAllDeviceCategory().then(response => {
this.deviceCategoryList = response?.data || []
})
},
// 下载点位清单
downloadPointDetail(command, isDetail = false) {
const siteId = isDetail ? command.siteId : this.siteId
const deviceCategory = isDetail ? command.deviceCategory : command.code
const categoryName = isDetail ? command.categoryName : command.name
const deviceId = isDetail ? command.deviceId : null
console.log('下载', command, isDetail)
this.download('ems/pointMatch/export', {
siteId,
deviceCategory,
deviceId,
}, `点位清单_${categoryName}_${new Date().getTime()}.xlsx`)
},
// 上传点位清单
uploadPointDetail(command, isDetail = false) {
const siteId = isDetail ? command.siteId : this.siteId
const deviceCategory = isDetail ? command.deviceCategory : command.code
const categoryName = isDetail ? command.categoryName : command.name
const deviceId = isDetail ? command.deviceId : ''
console.log('上传', command, isDetail)
this.$refs.pointUpload.showDialog({siteId, deviceCategory, categoryName, deviceId})
},
clearEditDeviceData() {
this.mode = '';
this.editDeviceId = ''
@ -342,26 +264,13 @@ export default {
}).finally(() => {
this.loading = false
})
},
//获取站点列表
getZdList() {
this.searchLoading = true
return getAllSites().then(response => {
this.siteList = response?.data || []
if (this.siteList.length > 0) this.siteId = this.siteList[0].siteId
}).finally(() => {
this.searchLoading = false
})
}
},
mounted() {
this.loading = true
this.siteId = ''
this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.pageNum = 1//每次搜索从1开始搜索
this.getDeviceCategoryList()
this.getZdList().then(() => {
this.getData()
})
this.getData()
}
}
</script>

View File

@ -3,7 +3,10 @@
<div class="header-row">
<div class="title-block">
<div class="page-title">单站监控项目点位配置</div>
<div class="page-desc">站点{{ siteName || siteId || '-' }}</div>
<div class="page-desc">
<span class="site-label">站点</span>
<span>{{ siteName || siteId || '-' }}</span>
</div>
</div>
<div>
<el-button @click="goBack">返回</el-button>
@ -34,15 +37,42 @@
<el-table-column prop="section" label="分组" min-width="180" />
<el-table-column prop="name" label="页面展示名称" min-width="280" />
<el-table-column prop="field" label="字段名" min-width="260" />
<el-table-column label="点位" min-width="420">
<el-table-column label="点位" min-width="300">
<template slot-scope="scope">
<el-input
v-model.trim="scope.row.point"
placeholder="请输入点位"
clearable
class="short-input"
/>
</template>
</el-table-column>
<el-table-column label="固定展示值" min-width="300">
<template slot-scope="scope">
<el-input
v-model.trim="scope.row.fixedValue"
placeholder="请输入固定展示值"
clearable
class="short-input"
/>
</template>
</el-table-column>
<el-table-column label="固定展示" width="120" align="center">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.useFixedDisplay" :true-label="1" :false-label="0" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="handleDeleteItem(scope.$index, topMenu.items)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-tabs
@ -61,15 +91,42 @@
<el-table-column prop="section" label="分组" min-width="180" />
<el-table-column prop="name" label="页面展示名称" min-width="280" />
<el-table-column prop="field" label="字段名" min-width="260" />
<el-table-column label="点位" min-width="420">
<el-table-column label="点位" min-width="300">
<template slot-scope="scope">
<el-input
v-model.trim="scope.row.point"
placeholder="请输入点位"
clearable
class="short-input"
/>
</template>
</el-table-column>
<el-table-column label="固定展示值" min-width="300">
<template slot-scope="scope">
<el-input
v-model.trim="scope.row.fixedValue"
placeholder="请输入固定展示值"
clearable
class="short-input"
/>
</template>
</el-table-column>
<el-table-column label="固定展示" width="120" align="center">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.useFixedDisplay" :true-label="1" :false-label="0" />
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="handleDeleteItem(scope.$index, child.items)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
@ -85,17 +142,25 @@ export default {
name: 'MonitorPointMapping',
data() {
return {
siteId: '',
siteName: '',
activeTopMenu: 'HOME',
activeChildMap: {},
topMenuList: []
topMenuList: [],
deletedFieldCodes: []
}
},
computed: {
siteId() {
return this.$route.query.siteId || ''
},
siteName() {
return this.$route.query.siteName || ''
watch: {
'$route.query.siteId': {
immediate: false,
async handler(newSiteId) {
if (newSiteId === this.siteId) {
return
}
this.siteId = newSiteId || ''
this.siteName = this.$route.query.siteName || this.siteId
await this.initMenuStructure()
}
}
},
methods: {
@ -105,6 +170,7 @@ export default {
async initMenuStructure() {
if (!this.siteId) {
this.topMenuList = []
this.deletedFieldCodes = []
return
}
const response = await getSingleMonitorProjectPointMapping(this.siteId)
@ -131,7 +197,9 @@ export default {
section: row.sectionName || '-',
name: row.fieldName || '-',
field: row.fieldCode || '',
point: row.dataPoint || ''
point: row.dataPoint || '',
fixedValue: row.fixedDataPoint || '',
useFixedDisplay: row.useFixedDisplay === 1 ? 1 : 0
}
const topItem = topMap.get(topKey)
const isTopDirect = moduleCode === menuCode || moduleCode === 'HOME'
@ -152,6 +220,7 @@ export default {
const firstTop = this.topMenuList[0]
this.activeTopMenu = firstTop ? firstTop.code : 'HOME'
this.activeChildMap = {}
this.deletedFieldCodes = []
this.topMenuList.forEach(top => {
if (top.children && top.children.length > 0) {
this.$set(this.activeChildMap, top.code, top.children[0].code)
@ -168,6 +237,23 @@ export default {
})
return rows
},
handleDeleteItem(index, list) {
if (!Array.isArray(list) || index < 0 || index >= list.length) {
return
}
this.$confirm('是否确认删除该条明细记录?删除后需点击“保存”才会生效。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const fieldCode = list[index] && list[index].field
if (fieldCode && !this.deletedFieldCodes.includes(fieldCode)) {
this.deletedFieldCodes.push(fieldCode)
}
list.splice(index, 1)
this.$message.success('已删除该条明细记录')
}).catch(() => {})
},
async saveData() {
if (!this.siteId) {
this.$message.warning('缺少站点ID')
@ -175,13 +261,29 @@ export default {
}
const mappings = this.getAllMappingRows()
.filter(item => item.field)
.map(item => ({ fieldCode: item.field, dataPoint: item.point || '' }))
await saveSingleMonitorProjectPointMapping({ siteId: this.siteId, mappings })
this.$message.success('保存成功')
.map(item => ({
fieldCode: item.field,
dataPoint: item.point || '',
fixedDataPoint: item.fixedValue || '',
useFixedDisplay: item.useFixedDisplay === 1 ? 1 : 0
}))
const response = await saveSingleMonitorProjectPointMapping({
siteId: this.siteId,
mappings,
deletedFieldCodes: this.deletedFieldCodes
})
const affectedRows = Number(response?.data || 0)
if (affectedRows > 0) {
this.$message.success(`保存成功,已写入 ${affectedRows}`)
} else {
this.$message.warning('保存完成,但写入 0 条,请检查字段编码和点位值是否有效')
}
await this.initMenuStructure()
}
},
async mounted() {
this.siteId = this.$route.query.siteId || ''
this.siteName = this.$route.query.siteName || this.siteId
await this.initMenuStructure()
}
}
@ -209,6 +311,16 @@ export default {
margin-top: 6px;
font-size: 13px;
color: #909399;
display: flex;
align-items: center;
}
.site-label {
margin-right: 8px;
}
.short-input {
width: 220px;
}
.child-menu-tabs {

View File

@ -59,11 +59,10 @@
</el-table-column>
<el-table-column
label="操作"
width="220"
width="120"
fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="openEditDialog(scope.row)">编辑</el-button>
<el-button type="text" size="small" @click="openPointMappingPage(scope.row)">配置</el-button>
</template>
</el-table-column>
</el-table>
@ -266,15 +265,6 @@ export default {
this.$refs.siteForm && this.$refs.siteForm.clearValidate()
})
},
openPointMappingPage(row) {
this.$router.push({
path: '/ems/site/zdlb/monitor-point-mapping',
query: {
siteId: row.siteId,
siteName: row.siteName || ''
}
})
},
submitSite() {
this.$refs.siteForm.validate(valid => {
if (!valid) {