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

@ -330,6 +330,23 @@ export function configStrategy(data) {
})
}
// 获取策略运行参数配置(按站点)
export function getStrategyRuntimeConfig(siteId) {
return request({
url: `/system/strategyRuntimeConfig/getBySiteId?siteId=${siteId}`,
method: 'get'
})
}
// 保存策略运行参数配置(按站点)
export function saveStrategyRuntimeConfig(data) {
return request({
url: `/system/strategyRuntimeConfig/save`,
method: 'post',
data
})
}
//http://localhost:8089/strategy/temp/getTempNameList?strategyId=1&siteId=021_FXX_01
//获取策略下的所有模板列表
export function getTempNameList({siteId, strategyId}) {

View File

@ -4,7 +4,10 @@
<el-row type="flex" >
<el-card shadow="hover" class="card common-card-container-body-no-padding" v-for="(item,index) in data" :key="index+'zdInfo'" :style="{borderBottomColor:item.color}">
<div class="info">{{ item.title }}</div>
<div class="num">{{item.num | formatNumber}}</div>
<div class="num">
<i v-if="item.loading" class="el-icon-loading"></i>
<span v-else>{{item.num | formatNumber}}</span>
</div>
</el-card>
</el-row>
</template>
@ -18,30 +21,35 @@ export default {
title:'站点总数(座)',
num:'',
color:'#FFBD00',
attr:'siteNum'
attr:'siteNum',
loading: true
},{
title:'装机功率MW',
num:'',
color:'#3C81FF',
attr:'installPower'
attr:'installPower',
loading: true
},{
title:'装机容量MW',
num:'',
color:'#5AC7C0',
attr:'installCapacity'
attr:'installCapacity',
loading: true
},{
title:'总充电量KWh',
num:'',
color:'#A696FF',
attr:'totalChargedCap'
attr:'totalChargedCap',
loading: true
},{
title:'总放电量KWh',
num:'',
color:'#A696FF',
attr:'totalDischargedCap'
attr:'totalDischargedCap',
loading: true
}]
}
@ -50,6 +58,7 @@ export default {
setData(res = {}){
this.data.forEach((item)=>{
item.num =res[item.attr]
item.loading = false
})
}
},

View File

@ -2,8 +2,16 @@
<template>
<div class="zd-select-container">
<el-form :inline="true">
<el-form-item label="站点选择">
<el-select v-model="id" placeholder="请选择换电站名称" :loading="loading" loading-text="正在加载数据" @change="onSubmit">
<el-form-item :label="showLabel ? '站点选择' : ''" :class="{'no-label': !showLabel}">
<el-select
v-model="id"
:size="size"
:placeholder="placeholder"
:loading="loading"
loading-text="正在加载数据"
:style="{width: selectWidth}"
@change="onSubmit"
>
<el-option :label="item.siteName" :value="item.siteId" v-for="(item,index) in siteList" :key="index+'zdxeSelect'"></el-option>
</el-select>
</el-form-item>
@ -15,7 +23,21 @@
</template>
<style scoped lang="scss">
.zd-select-container {
.el-form {
display: inline-flex;
align-items: center;
}
.el-form-item {
margin-bottom: 0;
}
.no-label ::v-deep .el-form-item__label {
display: none;
}
.no-label ::v-deep .el-form-item__content {
margin-left: 0 !important;
}
}
</style>
<script>
import {getAllSites} from '@/api/ems/zddt'
@ -31,6 +53,26 @@ import {mapGetters} from "vuex"
type:String,
default:'',
required:false
},
showLabel: {
type: Boolean,
default: true,
required: false
},
size: {
type: String,
default: 'medium',
required: false
},
placeholder: {
type: String,
default: '请选择换电站名称',
required: false
},
selectWidth: {
type: String,
default: '220px',
required: false
}
},
data() {
@ -44,6 +86,16 @@ import {mapGetters} from "vuex"
computed:{
...mapGetters(["zdList"]),
},
watch: {
defaultSiteId(newVal) {
if (!newVal || !this.siteList || this.siteList.length === 0) {
return
}
if (this.siteList.find(item => item.siteId === newVal) && this.id !== newVal) {
this.id = newVal
}
}
},
methods:{
onSubmit(){
this.$emit('submitSite',this.id)
@ -60,7 +112,6 @@ import {mapGetters} from "vuex"
getList(){
return getAllSites().then(response => {
this.siteList = response.data || []
console.log("获取站点列表返回数据",response,this.siteList)
this.setDefaultSite()
}).finally(() => {this.loading=false;this.searchLoading=false})
}
@ -73,13 +124,11 @@ import {mapGetters} from "vuex"
if(this.zdList.length === 0){
this.getList().then(()=>{
this.$store.commit('SET_ZD_LIST', this.siteList)
console.log("从store中获取站点列表数据,但是store中的zdList=[],所以从接口获取数据",this.zdList,this.siteList)
})
}else{
this.siteList = this.zdList
this.loading=false
this.searchLoading=false
console.log("从store中获取站点列表数据",this.zdList,this.siteList)
this.setDefaultSite()
}
}else{

View File

@ -22,6 +22,16 @@
</el-tooltip>
</template>
<div v-if="device!=='mobile'" class="site-select-wrap">
<zd-select
:get-list-by-store="true"
:show-label="false"
size="mini"
select-width="220px"
:default-site-id="$route.query.siteId"
@submitSite="onSiteChange"
/>
</div>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper">
@ -54,6 +64,7 @@ import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import BigDataPopup from '@/components/BigDataPopup'
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
export default {
emits: ['setLayout'],
@ -64,7 +75,8 @@ export default {
Screenfull,
SizeSelect,
Search,
BigDataPopup
BigDataPopup,
ZdSelect
},
computed: {
...mapGetters([
@ -85,6 +97,22 @@ export default {
}
},
methods: {
onSiteChange(id) {
if (!id) {
return
}
localStorage.setItem('global_site_id', id)
if (id !== this.$route.query.siteId) {
this.$router.push({
path: this.$route.path,
query: {
...this.$route.query,
siteId: id
}
})
}
this.$store.dispatch('getSiteAlarmNum', id)
},
showBigDataImg() {
this.$refs.bigDataPopup.show = true
},
@ -150,6 +178,22 @@ export default {
height: 100%;
line-height: 50px;
.site-select-wrap {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 10px 0 14px;
vertical-align: top;
::v-deep .el-form-item__content {
line-height: 1;
}
::v-deep .el-input__inner {
border-radius: 16px;
}
}
.big-data-container {
display: inline-block;
padding: 0 8px;
@ -215,6 +259,7 @@ export default {
}
}
}
}
}
</style>

View File

@ -10,11 +10,16 @@ import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register']
const GLOBAL_SITE_STORAGE_KEY = 'global_site_id'
const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
}
const shouldAppendSiteId = (path) => {
return !['/login', '/register', '/404', '/401'].includes(path) && !path.startsWith('/redirect')
}
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
@ -26,6 +31,24 @@ router.beforeEach((to, from, next) => {
} else if (isWhiteList(to.path)) {
next()
} else {
const routeSiteId = to.query?.siteId
if (routeSiteId) {
localStorage.setItem(GLOBAL_SITE_STORAGE_KEY, routeSiteId)
} else {
const globalSiteId = localStorage.getItem(GLOBAL_SITE_STORAGE_KEY)
if (globalSiteId && shouldAppendSiteId(to.path)) {
next({
path: to.path,
query: {
...to.query,
siteId: globalSiteId
},
hash: to.hash,
replace: true
})
return
}
}
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息

View File

@ -285,6 +285,28 @@ export const dzjk = [
activeSecondMenuName: 'DzjkClpz'
},
},
{
path: 'runtimeParam',
component: () => import('@/views/ems/dzjk/clpz/runtimeParam/index.vue'),
name: 'DzjkClpzRuntimeParam',
meta: {
title: '运行参数',
breadcrumb: false,
activeMenu: '/dzjk',
activeSecondMenuName: 'DzjkClpz'
},
},
{
path: 'sbbh',
component: () => import('@/views/ems/site/sbbh/index.vue'),
name: 'DzjkClpzSbbh',
meta: {
title: '设备保护',
breadcrumb: false,
activeMenu: '/dzjk',
activeSecondMenuName: 'DzjkClpz'
},
},
// {
// path: 'xftg',
// component: () => import('@/views/ems/dzjk/clpz/xftg/index.vue'),
@ -300,4 +322,3 @@ export const dzjk = [
]
}
]

View File

@ -98,7 +98,7 @@ export const constantRoutes = [
path: 'monitor-point-mapping',
component: () => import('@/views/ems/site/zdlb/MonitorPointMapping.vue'),
name: 'MonitorPointMapping',
meta: { title: '单站监控项目点位配置', activeMenu: '/ems/site/zdlb' }
meta: { title: '单站监控项目点位配置', activeMenu: '/dzjk/clpz/sbbh' }
}
]
},

View File

@ -50,4 +50,3 @@ export default {
flex: 1;
}
</style>

View File

@ -0,0 +1,137 @@
<template>
<div class="ems-dashboard-editor-container" v-loading="loading">
<el-card shadow="always" class="common-card-container">
<div slot="header" class="clearfix">
<span class="card-title">运行参数配置</span>
<span class="site-tag">站点{{ siteId || '-' }}</span>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="180px" style="max-width: 760px;">
<el-form-item label="SOC下限(%)" prop="socDown">
<el-input-number v-model="form.socDown" :min="0" :max="100" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="SOC上限(%)" prop="socUp">
<el-input-number v-model="form.socUp" :min="0" :max="100" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="防逆流阈值(kW)" prop="antiReverseThreshold">
<el-input-number v-model="form.antiReverseThreshold" :min="0" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="防逆流阈值上浮比例(%)" prop="antiReverseRangePercent">
<el-input-number v-model="form.antiReverseRangePercent" :min="0" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="防逆流恢复上限(kW)" prop="antiReverseUp">
<el-input-number v-model="form.antiReverseUp" :min="0" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="防逆流降功率比例(%)" prop="antiReversePowerDownPercent">
<el-input-number v-model="form.antiReversePowerDownPercent" :min="0" :max="100" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item label="防逆流硬停阈值(kW)" prop="antiReverseHardStopThreshold">
<el-input-number v-model="form.antiReverseHardStopThreshold" :min="0" :step="1" :precision="2" controls-position="right" style="width: 220px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saveLoading" @click="handleSave">保存</el-button>
<el-button @click="init">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
import getQuerySiteId from '@/mixins/ems/getQuerySiteId'
import { getStrategyRuntimeConfig, saveStrategyRuntimeConfig } from '@/api/ems/dzjk'
const emptyForm = () => ({
siteId: '',
socDown: 0,
socUp: 100,
antiReverseThreshold: 30,
antiReverseRangePercent: 20,
antiReverseUp: 100,
antiReversePowerDownPercent: 10,
antiReverseHardStopThreshold: 20
})
export default {
name: 'DzjkClpzRuntimeParam',
mixins: [getQuerySiteId],
data() {
return {
loading: false,
saveLoading: false,
form: emptyForm(),
rules: {
socDown: [
{ required: true, message: '请输入SOC下限', trigger: 'change' }
],
socUp: [
{ required: true, message: '请输入SOC上限', trigger: 'change' }
],
antiReverseThreshold: [
{ required: true, message: '请输入防逆流阈值', trigger: 'change' }
],
antiReverseRangePercent: [
{ required: true, message: '请输入防逆流阈值上浮比例', trigger: 'change' }
],
antiReverseUp: [
{ required: true, message: '请输入防逆流恢复上限', trigger: 'change' }
],
antiReversePowerDownPercent: [
{ required: true, message: '请输入防逆流降功率比例', trigger: 'change' }
],
antiReverseHardStopThreshold: [
{ required: true, message: '请输入防逆流硬停阈值', trigger: 'change' }
]
}
}
},
methods: {
init() {
if (!this.siteId) {
this.form = emptyForm()
return
}
this.loading = true
getStrategyRuntimeConfig(this.siteId).then(response => {
const data = response?.data || {}
this.form = {
...emptyForm(),
...data,
siteId: this.siteId
}
}).finally(() => {
this.loading = false
})
},
handleSave() {
if (!this.siteId) {
this.$message.error('缺少站点ID')
return
}
this.$refs.form.validate(valid => {
if (!valid) return
if (Number(this.form.socDown) > Number(this.form.socUp)) {
this.$message.error('SOC下限不能大于SOC上限')
return
}
this.saveLoading = true
saveStrategyRuntimeConfig({ ...this.form, siteId: this.siteId }).then(response => {
if (response?.code === 200) {
this.$message.success('保存成功')
this.init()
}
}).finally(() => {
this.saveLoading = false
})
})
}
}
}
</script>
<style scoped lang="scss">
.site-tag {
float: right;
color: #909399;
font-size: 13px;
}
</style>

View File

@ -9,14 +9,6 @@
<el-form-item label="soc限制" prop="sdcLimit" required>
<el-switch :active-value="1" :inactive-value="0" v-model="formData.sdcLimit"></el-switch>
</el-form-item>
<!-- <template v-if="formData.sdcLimit === 1">-->
<el-form-item label="soc下限" prop="sdcDown">
<el-input v-model="formData.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="soc上限" prop="sdcUp">
<el-input v-model="formData.sdcUp" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<!-- </template>-->
</el-form>
<el-button type="primary" size="mini" @click="addTime">新增</el-button>
<!-- 新增时间段表单-->
@ -64,6 +56,12 @@
<el-input v-model="formInline.chargeDischargePower" placeholder="请输入"
:style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="soc下限" prop="sdcDown">
<el-input v-model="formInline.sdcDown" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="soc上限" prop="sdcUp">
<el-input v-model="formInline.sdcUp" placeholder="请输入" clearable :style="{width: '100%'}"></el-input>
</el-form-item>
<el-form-item label="充电状态" prop="chargeStatus">
<el-select v-model="formInline.chargeStatus" placeholder="请选择" :style="{width: '100%'}">
<el-option v-for="(value,key) in chargeStatusOptions" :key="key+'chargeStatusOptions'" :label="value"
@ -84,20 +82,100 @@
<el-table-column
prop="startTime"
label="开始时间">
<template slot-scope="scope">
<el-time-select
v-if="mode === 'edit'"
v-model="scope.row.startTime"
placeholder="开始时间"
:picker-options="{
start: '00:00',
step: '00:01',
end: '23:59'
}"
:style="{width: '100%'}"
/>
<span v-else>{{ scope.row.startTime || '-' }}</span>
</template>
</el-table-column>
<el-table-column
prop="endTime"
label="结束时间">
<template slot-scope="scope">
<el-time-select
v-if="mode === 'edit'"
v-model="scope.row.endTime"
placeholder="结束时间"
:picker-options="{
start: '00:00',
step: '00:01',
end: '23:59',
minTime: scope.row.startTime
}"
:style="{width: '100%'}"
/>
<span v-else>{{ scope.row.endTime || '-' }}</span>
</template>
</el-table-column>
<el-table-column
prop="chargeDischargePower"
label="充放功率kW">
<template slot-scope="scope">
<el-input
v-if="mode === 'edit'"
v-model.trim="scope.row.chargeDischargePower"
placeholder="请输入"
clearable
:style="{width: '100%'}"
/>
<span v-else>{{ scope.row.chargeDischargePower || '-' }}</span>
</template>
</el-table-column>
<el-table-column
prop="sdcDown"
label="SOC下限">
<template slot-scope="scope">
<el-input
v-if="mode === 'edit'"
v-model.trim="scope.row.sdcDown"
placeholder="请输入"
clearable
:style="{width: '100%'}"
/>
<span v-else>{{ scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%' }}</span>
</template>
</el-table-column>
<el-table-column
prop="sdcUp"
label="SOC上限">
<template slot-scope="scope">
<el-input
v-if="mode === 'edit'"
v-model.trim="scope.row.sdcUp"
placeholder="请输入"
clearable
:style="{width: '100%'}"
/>
<span v-else>{{ scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%' }}</span>
</template>
</el-table-column>
<el-table-column
prop="chargeStatus"
label="充电状态">
<template slot-scope="scope">
{{ chargeStatusOptions[scope.row.chargeStatus] }}
<el-select
v-if="mode === 'edit'"
v-model="scope.row.chargeStatus"
placeholder="请选择"
:style="{width: '100%'}"
>
<el-option
v-for="(value,key) in chargeStatusOptions"
:key="key+'chargeStatusEditOptions'"
:label="value"
:value="key"
/>
</el-select>
<span v-else>{{ chargeStatusOptions[scope.row.chargeStatus] }}</span>
</template>
</el-table-column>
<el-table-column
@ -138,8 +216,6 @@ export default {
formData: {
templateName: '',
sdcLimit: false,
sdcDown: '',
sdcUp: '',
},
rules: {
templateName: [{
@ -147,19 +223,13 @@ export default {
message: '请输入',
trigger: 'blur'
}],
sdcDown: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
sdcUp: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
},
showAddTime: false,
formInline: {
timeRange: range,
chargeDischargePower: '',
sdcDown: '',
sdcUp: '',
chargeStatus: ''
},
formInlineRule: {
@ -175,6 +245,14 @@ export default {
},
{pattern: /^-?\d*\.?\d*$/, message: '请输入合法数字或小数'}
],
sdcDown: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
sdcUp: [
{required: true, message: '请输入', trigger: 'blur'},
{pattern: /^(0|[1-9]\d*)(\.\d+)?$/, message: '请输入合法数字或小数'}
],
chargeStatus: [{
required: true,
message: '请选择充放状态',
@ -198,11 +276,9 @@ export default {
this.formData = {
templateName: '',
sdcLimit: false,
sdcDown: '',
sdcUp: '',
}
this.formInline = {
timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''
timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''
}//startTime: '', endTime: '',
this.showAddTime = false
this.tableData = []
@ -216,14 +292,12 @@ export default {
getStrategyTempDetail(this.editTempId).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || []));
if (data.length > 0) {
const {templateName, sdcLimit, sdcDown, sdcUp} = JSON.parse(JSON.stringify(data[0]));
const {templateName, sdcLimit} = JSON.parse(JSON.stringify(data[0]));
this.formData.templateName = templateName
this.formData.sdcLimit = sdcLimit
this.formData.sdcDown = sdcDown
this.formData.sdcUp = sdcUp
}
if (data.length === 1) {
const {startTime, endTime} = data;
const {startTime, endTime} = data[0];
if (!startTime || !endTime) {
this.tableData = []
} else {
@ -242,15 +316,15 @@ export default {
cancelAddTime() {
this.$refs.addTimeForm.resetFields()
this.showAddTime = false
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''}//startTime: '', endTime: '',
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''}//startTime: '', endTime: '',
},
saveTime() {
//表单校验校验成功添加到tableData里
this.$refs.addTimeForm.validate(valid => {
if (!valid) return
const {timeRange: [startTime, endTime], chargeDischargePower, chargeStatus} = this.formInline
const {timeRange: [startTime, endTime], chargeDischargePower, sdcDown, sdcUp, chargeStatus} = this.formInline
this.tableData.push({startTime, endTime, chargeDischargePower, chargeStatus})
this.tableData.push({startTime, endTime, chargeDischargePower, sdcDown, sdcUp, chargeStatus})
this.$nextTick(() => {
this.cancelAddTime()
})
@ -262,9 +336,14 @@ export default {
saveDialog() {
this.$refs.addTempForm.validate(valid => {
if (!valid) return
const {templateName, sdcLimit, sdcDown, sdcUp} = this.formData
const {templateName, sdcLimit} = this.formData
const {siteId, updateStrategyId} = this.$home
const {tableData} = this
const tableData = this.tableData.map(item => ({
...item,
sdcDown: this.normalizeSocValue(item.sdcDown),
sdcUp: this.normalizeSocValue(item.sdcUp)
}))
if (!this.validateTableData(tableData)) return
if (this.mode === 'edit') {
editStrategyTemp({
siteId,
@ -272,8 +351,6 @@ export default {
templateId: this.editTempId,
templateName,
sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData
}).then(response => {
if (response?.code === 200) {
@ -288,8 +365,6 @@ export default {
strategyId: updateStrategyId,
templateName,
sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData
}).then(response => {
if (response?.code === 200) {
@ -300,14 +375,64 @@ export default {
}
})
},
normalizeSocValue(value) {
if (value === null || value === undefined) return null
const normalized = String(value).replace('%', '').trim()
return normalized === '' ? null : normalized
},
toMinutes(timeValue) {
if (!timeValue || String(timeValue).indexOf(':') < 0) return -1
const [h, m] = String(timeValue).split(':')
const hour = Number(h), minute = Number(m)
if (!Number.isInteger(hour) || !Number.isInteger(minute)) return -1
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return -1
return hour * 60 + minute
},
validateTableData(list = []) {
const numberPattern = /^-?\d+(\.\d+)?$/
const socPattern = /^(0|[1-9]\d*)(\.\d+)?$/
for (let i = 0; i < list.length; i++) {
const row = list[i]
const rowNo = i + 1
if (!row.startTime || !row.endTime) {
this.$message.error(`${rowNo}行:开始时间和结束时间不能为空`)
return false
}
const startMinute = this.toMinutes(row.startTime)
const endMinute = this.toMinutes(row.endTime)
if (startMinute < 0 || endMinute < 0 || startMinute >= endMinute) {
this.$message.error(`${rowNo}行:时间范围不合法`)
return false
}
if (!numberPattern.test(String(row.chargeDischargePower ?? '').trim())) {
this.$message.error(`${rowNo}行:充放功率格式不正确`)
return false
}
if (!socPattern.test(String(row.sdcDown ?? '').trim())) {
this.$message.error(`${rowNo}SOC下限格式不正确`)
return false
}
if (!socPattern.test(String(row.sdcUp ?? '').trim())) {
this.$message.error(`${rowNo}SOC上限格式不正确`)
return false
}
if (Number(row.sdcDown) > Number(row.sdcUp)) {
this.$message.error(`${rowNo}SOC下限不能大于SOC上限`)
return false
}
if (row.chargeStatus === undefined || row.chargeStatus === null || row.chargeStatus === '') {
this.$message.error(`${rowNo}行:请选择充电状态`)
return false
}
}
return true
},
closeDialog() {
// 清空所有数据
this.$refs.addTempForm.resetFields()
this.formData = {
templateName: '',
sdcLimit: 0,
sdcDown: '',
sdcUp: '',
}
this.tableData = []
this.cancelAddTime()

View File

@ -39,14 +39,14 @@
prop="sdcDown"
label="SOC下限">
<template slot-scope="scope">
{{scope.row.sdcDown ? scope.row. sdcDown + '%' : '-'}}
{{scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%'}}
</template>
</el-table-column>
<el-table-column
prop="sdcUp"
label="SOC上限">
<template slot-scope="scope">
{{scope.row.sdcUp ? scope.row.sdcUp + '%' : '-'}}
{{scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%'}}
</template>
</el-table-column>
<el-table-column
@ -92,7 +92,7 @@ export default {
activeBtn:'',
tempList:[],
tableData:[],
mixinPrototype:['templateName','sdcLimit','sdcDown','sdcUp']
mixinPrototype:['templateName','sdcLimit']
}
},
computed:{

View File

@ -12,7 +12,8 @@
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
import {getPointData} from '@/api/ems/dzjk'
import {getProjectDisplayData} from '@/api/ems/dzjk'
import {getPointConfigCurve} from '@/api/ems/site'
import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default {
@ -48,8 +49,34 @@ export default {
getGVQXData() {
this.showLoading()
const {siteId, timeRange} = this
getPointData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
this.setOption(response?.data || [])
getProjectDisplayData(siteId).then(response => {
const displayData = response?.data || []
const sectionRows = displayData.filter(item =>
item && item.sectionName === '当日功率曲线' && item.useFixedDisplay !== 1 && item.dataPoint
)
const tasks = sectionRows.map(row => {
const pointId = String(row.dataPoint || '').trim()
if (!pointId) return Promise.resolve(null)
return getPointConfigCurve({
siteId,
pointId,
pointType: 'data',
rangeType: 'custom',
startTime: this.normalizeDateTime(timeRange[0], false),
endTime: this.normalizeDateTime(timeRange[1], true)
}).then(curveResponse => {
const list = curveResponse?.data || []
return {
name: row.fieldName || row.fieldCode || pointId,
data: list
.map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
.filter(item => item[0] && !Number.isNaN(item[1]))
}
}).catch(() => null)
})
return Promise.all(tasks)
}).then(series => {
this.setOption((series || []).filter(Boolean))
}).finally(() => this.hideLoading())
},
init(siteId) {
@ -70,12 +97,18 @@ export default {
hideLoading() {
this.chart && this.chart.hideLoading()
},
setOption(data) {
const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率', 'soc平均值', 'soh平均值', '电池平均温度平均值']]
console.log('source.slice(1)', source[0].slice(1))
this.chart && data.forEach((item) => {
source.push([item.statisDate, item.gridPower, item.loadPower, item.storagePower, item.pvPower, item.avgSoc, item.avgSoh, item.avgTemp])
})
normalizeDateTime(value, endOfDay) {
const raw = String(value || '').trim()
if (!raw) return ''
if (raw.includes(' ')) return raw
return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}`
},
parseToTimestamp(value) {
if (!value) return null
const t = new Date(value).getTime()
return Number.isNaN(t) ? null : t
},
setOption(seriesData = []) {
this.chart.setOption({
grid: {
containLabel: true
@ -86,35 +119,28 @@ export default {
},
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
axisPointer: { type: 'cross' }
},
textStyle: {
color: "#333333",
},
xAxis: {
type: 'category',
type: 'time',
},
yAxis: [
{
yAxis: [{
type: 'value',
},
{
type: 'value',
},
],
dataset: {source},
series: source[0].slice(1).map((item, index) => {
}],
series: seriesData.map((item) => {
return {
type: 'line',//index === 5 ? 'bar' : 'line',
name: item.name,
type: 'line',
showSymbol: false,
symbolSize: 2,
smooth: true,
areaStyle: {
opacity: 0.5,
},
yAxisIndex: index <= 4 ? 0 : 1
data: item.data
}
})
})
@ -124,4 +150,3 @@ export default {
}
</script>

View File

@ -12,7 +12,8 @@
import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
import {getSevenChargeData} from '@/api/ems/dzjk'
import {getProjectDisplayData} from '@/api/ems/dzjk'
import {getPointConfigCurve} from '@/api/ems/site'
export default {
mixins: [resize],
@ -45,8 +46,34 @@ export default {
getWeekKData() {
this.showLoading()
const {siteId, timeRange} = this
getSevenChargeData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => {
this.setOption(response?.data || [])
getProjectDisplayData(siteId).then(response => {
const displayData = response?.data || []
const sectionRows = displayData.filter(item =>
item && item.sectionName === '一周充放曲线' && item.useFixedDisplay !== 1 && item.dataPoint
)
const tasks = sectionRows.map(row => {
const pointId = String(row.dataPoint || '').trim()
if (!pointId) return Promise.resolve(null)
return getPointConfigCurve({
siteId,
pointId,
pointType: 'data',
rangeType: 'custom',
startTime: this.normalizeDateTime(timeRange[0], false),
endTime: this.normalizeDateTime(timeRange[1], true)
}).then(curveResponse => {
const list = curveResponse?.data || []
return {
name: row.fieldName || row.fieldCode || pointId,
data: list
.map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
.filter(item => item[0] && !Number.isNaN(item[1]))
}
}).catch(() => null)
})
return Promise.all(tasks)
}).then(series => {
this.setOption((series || []).filter(Boolean))
}).finally(() => this.hideLoading())
},
init(siteId) {
@ -65,18 +92,23 @@ export default {
hideLoading() {
this.chart && this.chart.hideLoading()
},
setOption(data, unit) {
const source = [['日期', '充电量', '放电量']]
data.forEach(item => {
source.push([item.ammeterDate, item.chargedCap, item.disChargedCap])
})
normalizeDateTime(value, endOfDay) {
const raw = String(value || '').trim()
if (!raw) return ''
if (raw.includes(' ')) return raw
return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}`
},
parseToTimestamp(value) {
if (!value) return null
const t = new Date(value).getTime()
return Number.isNaN(t) ? null : t
},
setOption(seriesData = []) {
this.chart && this.chart.setOption({
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
axisPointer: { type: 'cross' }
},
grid: {
containLabel: true
@ -86,9 +118,7 @@ export default {
bottom: '15',
},
xAxis: {
type: 'category',
name: unit,
nameLocation: 'center'
type: 'time'
},
yAxis: [{
type: 'value',
@ -100,19 +130,12 @@ export default {
onZero: false
}
}],
dataset: {
source
},
series: [
{
series: seriesData.map(item => ({
name: item.name,
yAxisIndex: 0,
type: 'bar',
},
{
yAxisIndex: 0,
type: 'bar',
},
]
data: item.data
}))
})
}
}

View File

@ -1,5 +1,5 @@
<template>
<div v-loading="loading">
<div>
<el-row style="background: #fff" class="row-container" :gutter="15">
<el-col :xs="24" :sm="24" :lg="5">
<!-- 站点信息-->
@ -21,13 +21,19 @@
<div class="title">
<i class="el-icon-location"></i>
</div>
<div class="value">{{ info.siteAddress }}</div>
<div class="value">
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
<span v-else>{{ info.siteAddress || '-' }}</span>
</div>
</div>
<div class="site-info">
<div class="title">
<i class="el-icon-date"></i>
</div>
<div class="value">{{ info.runningTime || '-' }}</div>
<div class="value">
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
<span v-else>{{ info.runningTime || '-' }}</span>
</div>
</div>
<!-- 装机功率容量 -->
<el-row :gutter="10" style="margin-top:20px;">
@ -38,7 +44,8 @@
<div class="sjgl-wrapper">
<div class="sjgl-title">装机功率(MW)</div>
<div class="sjgl-value">
{{ info.installPower | formatNumber }}
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
<span v-else>{{ info.installPower | formatNumber }}</span>
</div>
</div>
</el-col>
@ -49,7 +56,8 @@
<div class="sjgl-wrapper">
<div class="sjgl-title">装机容量(MW)</div>
<div class="sjgl-value">
{{ info.installCapacity | formatNumber }}
<i v-if="isBaseInfoLoading" class="el-icon-loading"></i>
<span v-else>{{ info.installCapacity | formatNumber }}</span>
</div>
</div>
</el-col>
@ -67,7 +75,10 @@
<span class="card-title">总累计运行数据</span>
<div class="total-count">
<span class="title">总收入</span>
<span class="value">{{ runningInfo.totalRevenue | formatNumber }}</span>
<span class="value">
<i v-if="isRunningInfoLoading" class="el-icon-loading"></i>
<span v-else>{{ totalRevenueDisplayValue | formatNumber }}</span>
</span>
<span class="unit"></span>
</div>
</div>
@ -77,14 +88,15 @@
<el-row :gutter="10">
<el-col
:span="6"
v-for="(item, index) in sjglData"
v-for="(item, index) in runningDataCards"
:key="index + 'sjglData'"
class="sjgl-col"
>
<div class="sjgl-wrapper">
<div class="sjgl-title">{{ item.title }}</div>
<div class="sjgl-value" :style="{color:item.color}">
{{ runningInfo[item.attr] | formatNumber }}
<i v-if="item.loading" class="el-icon-loading"></i>
<span v-else>{{ item.value | formatNumber }}</span>
</div>
</div>
</el-col>
@ -104,7 +116,7 @@
<script>
import {getSingleSiteBaseInfo} from "@/api/ems/zddt";
import {getDzjkHomeView} from "@/api/ems/dzjk";
import {getDzjkHomeView, getProjectDisplayData} from "@/api/ems/dzjk";
import WeekChart from "./WeekChart.vue";
import ActiveChart from "./ActiveChart.vue";
import AlarmTable from "./AlarmTable.vue";
@ -119,7 +131,9 @@ export default {
data() {
return {
loading: false,
sjglData: [
baseInfoLoading: false,
runningInfoLoading: false,
fallbackSjglData: [
{
title: "今日充电量kWh",
attr: "dayChargedCap",
@ -163,38 +177,106 @@ export default {
],
info: {}, //基本信息
runningInfo: {}, //总累计运行数据+报警表格
runningDisplayData: [], //单站监控项目配置展示数据
};
},
computed: {
isBaseInfoLoading() {
const state = this.$data || {};
return !!(state.baseInfoLoading || state.loading);
},
isRunningInfoLoading() {
const state = this.$data || {};
return !!(state.runningInfoLoading || state.loading);
},
tableData() {
return this.runningInfo?.siteMonitorHomeAlarmVo || [];
},
totalRunningSectionData() {
return (this.runningDisplayData || []).filter(item => item.sectionName === "总累计运行数据");
},
totalRevenueDisplayItem() {
const sectionData = this.totalRunningSectionData || [];
const byFieldCode = sectionData.find(item => item.fieldCode === "totalRevenue");
if (byFieldCode) {
return byFieldCode;
}
return sectionData.find(item => item.fieldName === "总收入");
},
totalRevenueDisplayValue() {
return this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldValue : this.runningInfo.totalRevenue;
},
runningDataCards() {
const sectionData = this.totalRunningSectionData || [];
if (sectionData.length > 0) {
const revenueFieldCode = this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldCode : "";
return sectionData
.filter(item => item.fieldCode !== revenueFieldCode)
.map((item, index) => ({
title: item.fieldName,
value: item.fieldValue,
color: this.getCardColor(index),
loading: this.isRunningInfoLoading,
}));
}
return this.fallbackSjglData.map(item => ({
title: item.title,
value: this.runningInfo[item.attr],
color: item.color,
loading: this.isRunningInfoLoading,
}));
},
},
methods: {
setBaseInfoLoading(loading) {
if (Object.prototype.hasOwnProperty.call(this.$data, "baseInfoLoading")) {
this.baseInfoLoading = loading;
return;
}
this.$set(this.$data, "baseInfoLoading", loading);
},
setRunningInfoLoading(loading) {
if (Object.prototype.hasOwnProperty.call(this.$data, "runningInfoLoading")) {
this.runningInfoLoading = loading;
return;
}
this.$set(this.$data, "runningInfoLoading", loading);
},
getCardColor(index) {
const colors = ['#4472c4', '#70ad47', '#4472c4', '#f67438', '#4472c4', '#70ad47', '#70ad47', '#f67438'];
return colors[index % colors.length];
},
toAlarm() {
this.$router.push({path: "/dzjk/gzgj", query: this.$route.query});
},
getBaseInfo() {
this.setBaseInfoLoading(true);
return getSingleSiteBaseInfo(this.siteId).then((response) => {
this.info = response?.data || {};
}).finally(() => {
this.setBaseInfoLoading(false);
});
},
getRunningInfo() {
return getDzjkHomeView(this.siteId).then((response) => {
this.runningInfo = response?.data || {};
this.setRunningInfoLoading(true);
return Promise.all([
getDzjkHomeView(this.siteId),
getProjectDisplayData(this.siteId),
]).then(([homeResponse, displayResponse]) => {
this.runningInfo = homeResponse?.data || {};
this.runningDisplayData = displayResponse?.data || [];
}).finally(() => {
this.setRunningInfoLoading(false);
});
},
init() {
this.loading = true;
// 功率曲线
this.$refs.activeChart.init(this.siteId);
// 一周冲放曲线
this.$refs.weekChart.init(this.siteId);
// 静态信息 this.getBaseInfo()
// 总累计运行数据+故障告警 this.getRunningInfo()
Promise.all([this.getBaseInfo(), this.getRunningInfo()]).finally(() => {
this.loading = false;
});
Promise.all([this.getBaseInfo(), this.getRunningInfo()]);
// 一分钟循环一次总累计运行数据
this.updateInterval(this.getRunningInfo);
},

View File

@ -2,7 +2,6 @@
<template>
<div class="ems-dashboard-editor-container">
<zd-select :get-list-by-store="true" :default-site-id="$route.query.siteId" @submitSite="submitSite"/>
<el-menu
class="ems-second-menu"
:default-active="$route.meta.activeSecondMenuName"
@ -28,11 +27,8 @@
<script>
import { dzjk } from '@/router/ems'
const childrenRoute = dzjk[0].children[0].children//获取到单站监控下面的字路由
console.log('childrenRoute',childrenRoute)
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
import {mapState} from "vuex";
export default {
components:{ZdSelect},
data(){
return {
childrenRoute,
@ -44,18 +40,6 @@ export default {
dzjkAlarmLighting:state=>state.ems.dzjkAlarmLighting
})
},
methods:{
submitSite(id){
if(id !== this.$route.query.siteId){
// console.log('单站监控选择了其他的站点id=',id,'并更新页面地址参数')
this.$router.push({query:{...this.$route.query,siteId:id}})
}else{
// console.log('单站监控选择了相同的其他的站点id=',id,'页面地址不发生改变')
}
//获取告警列表数据
this.$store.dispatch('getSiteAlarmNum',id)
}
},
beforeRouteLeave(to,from, next){
//从单站监控下面的所有子页面跳出时会触发
// 清空store中的zdList 保障下次进入到单站监控会重新调用接口获取数据
@ -66,6 +50,9 @@ export default {
</script>
<style scoped lang="scss">
.ems-dashboard-editor-container{
padding-top: 12px;
}
.dzjk-ems-content-container{
margin-top:0;
min-height: 60vh;

View File

@ -1,25 +1,25 @@
<template>
<div class="ems-dashboard-editor-container" v-loading="loading">
<div class="ems-dashboard-editor-container">
<zd-info></zd-info>
<div class="ems-content-container ems-content-container-padding">
<div class="content-title">数据概览</div>
<el-row :gutter="15" style="background:#fff;margin:30px 0;">
<el-col :xs="24" :sm="12" :lg="12">
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.dlzbchart">
<dlzb-chart ref="dlzbchart"/>
</el-col>
<el-col :xs="24" :sm="12" :lg="12">
<el-col :xs="24" :sm="12" :lg="12" v-loading="chartLoading.xtxlchart">
<xtxl-chart ref="xtxlchart"/>
</el-col>
</el-row>
<el-row :gutter="15" style="background:#fff;margin:0;">
<el-col :xs="24" :sm="8" :lg="8">
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjqsChart">
<gjqs-chart ref="gjqsChart"/>
</el-col>
<el-col :xs="24" :sm="8" :lg="8">
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.sbgjzbChart">
<sbgjzb-chart ref="sbgjzbChart"/>
</el-col>
<el-col :xs="24" :sm="8" :lg="8">
<el-col :xs="24" :sm="8" :lg="8" v-loading="chartLoading.gjdjfbChart">
<gjdjfb-chart ref="gjdjfbChart"/>
</el-col>
</el-row>
@ -47,24 +47,45 @@ export default {
},
data() {
return {
loading:false,
chartLoading: {
dlzbchart: false,
xtxlchart: false,
gjqsChart: false,
sbgjzbChart: false,
gjdjfbChart: false
}
}
},
methods: {
setChartLoading(loading) {
this.chartLoading = {
dlzbchart: loading,
xtxlchart: loading,
gjqsChart: loading,
sbgjzbChart: loading,
gjdjfbChart: loading
}
},
hideChartLoading(key) {
this.$set(this.chartLoading, key, false)
}
},
mounted() {
this.loading = true
this.setChartLoading(true)
dataList().then(response => {
const data = JSON.parse(JSON.stringify(response?.data || {}))
this.$refs.dlzbchart.initChart(data?.elecDataList || [])
this.hideChartLoading('dlzbchart')
this.$refs.xtxlchart.initChart(data?.sysEfficList || [])
this.hideChartLoading('xtxlchart')
this.$refs.gjqsChart.initChart(data?.alarmDataList || [])
this.hideChartLoading('gjqsChart')
this.$refs.sbgjzbChart.initChart(data?.deviceAlarmList || [])
this.hideChartLoading('sbgjzbChart')
this.$refs.gjdjfbChart.initChart(data?.alarmLevelList || [])
}).finally(() => {
this.loading = false
this.hideChartLoading('gjdjfbChart')
}).catch(() => {
this.setChartLoading(false)
})
}
}

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,27 +487,6 @@ 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 => ({
@ -568,7 +499,6 @@ export default {
}).finally(() => {
this.loading = false
})
}
},
loadLatestValues() {
if (!this.tableData.length) {
@ -582,9 +512,57 @@ export default {
dataKey: item.dataKey
}))
if (!points.length) {
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 => {
const latestList = response?.data || []
const latestMap = latestList.reduce((acc, item) => {
@ -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 || ''}`
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
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)
}
}).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.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,8 +3,9 @@
:show-close="false" destroy-on-close lock-scroll append-to-body width="800px" class="ems-dialog"
:title="mode === 'add'?'新增设备':`编辑设备` ">
<div v-loading="loading>0">
<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">
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"
@ -83,14 +84,10 @@
<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">
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%'}">
@ -116,6 +113,10 @@
<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>
@ -131,6 +132,7 @@
</template>
</el-form>
</div>
</div>
<div slot="footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveDialog">确定</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()
})
}
}
</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) {