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 //http://localhost:8089/strategy/temp/getTempNameList?strategyId=1&siteId=021_FXX_01
//获取策略下的所有模板列表 //获取策略下的所有模板列表
export function getTempNameList({siteId, strategyId}) { export function getTempNameList({siteId, strategyId}) {

View File

@ -4,7 +4,10 @@
<el-row type="flex" > <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}"> <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="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-card>
</el-row> </el-row>
</template> </template>
@ -18,30 +21,35 @@ export default {
title:'站点总数(座)', title:'站点总数(座)',
num:'', num:'',
color:'#FFBD00', color:'#FFBD00',
attr:'siteNum' attr:'siteNum',
loading: true
},{ },{
title:'装机功率MW', title:'装机功率MW',
num:'', num:'',
color:'#3C81FF', color:'#3C81FF',
attr:'installPower' attr:'installPower',
loading: true
},{ },{
title:'装机容量MW', title:'装机容量MW',
num:'', num:'',
color:'#5AC7C0', color:'#5AC7C0',
attr:'installCapacity' attr:'installCapacity',
loading: true
},{ },{
title:'总充电量KWh', title:'总充电量KWh',
num:'', num:'',
color:'#A696FF', color:'#A696FF',
attr:'totalChargedCap' attr:'totalChargedCap',
loading: true
},{ },{
title:'总放电量KWh', title:'总放电量KWh',
num:'', num:'',
color:'#A696FF', color:'#A696FF',
attr:'totalDischargedCap' attr:'totalDischargedCap',
loading: true
}] }]
} }
@ -50,6 +58,7 @@ export default {
setData(res = {}){ setData(res = {}){
this.data.forEach((item)=>{ this.data.forEach((item)=>{
item.num =res[item.attr] item.num =res[item.attr]
item.loading = false
}) })
} }
}, },

View File

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

View File

@ -22,6 +22,16 @@
</el-tooltip> </el-tooltip>
</template> </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"> <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper"> <div class="avatar-wrapper">
@ -54,6 +64,7 @@ import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch' import Search from '@/components/HeaderSearch'
import BigDataPopup from '@/components/BigDataPopup' import BigDataPopup from '@/components/BigDataPopup'
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
export default { export default {
emits: ['setLayout'], emits: ['setLayout'],
@ -64,7 +75,8 @@ export default {
Screenfull, Screenfull,
SizeSelect, SizeSelect,
Search, Search,
BigDataPopup BigDataPopup,
ZdSelect
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
@ -85,6 +97,22 @@ export default {
} }
}, },
methods: { 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() { showBigDataImg() {
this.$refs.bigDataPopup.show = true this.$refs.bigDataPopup.show = true
}, },
@ -150,6 +178,22 @@ export default {
height: 100%; height: 100%;
line-height: 50px; 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 { .big-data-container {
display: inline-block; display: inline-block;
padding: 0 8px; padding: 0 8px;
@ -215,6 +259,7 @@ export default {
} }
} }
} }
} }
} }
</style> </style>

View File

@ -10,11 +10,16 @@ import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register'] const whiteList = ['/login', '/register']
const GLOBAL_SITE_STORAGE_KEY = 'global_site_id'
const isWhiteList = (path) => { const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, 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) => { router.beforeEach((to, from, next) => {
NProgress.start() NProgress.start()
if (getToken()) { if (getToken()) {
@ -26,6 +31,24 @@ router.beforeEach((to, from, next) => {
} else if (isWhiteList(to.path)) { } else if (isWhiteList(to.path)) {
next() next()
} else { } 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) { if (store.getters.roles.length === 0) {
isRelogin.show = true isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息 // 判断当前用户是否已拉取完user_info信息

View File

@ -285,6 +285,28 @@ export const dzjk = [
activeSecondMenuName: 'DzjkClpz' 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', // path: 'xftg',
// component: () => import('@/views/ems/dzjk/clpz/xftg/index.vue'), // 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', path: 'monitor-point-mapping',
component: () => import('@/views/ems/site/zdlb/MonitorPointMapping.vue'), component: () => import('@/views/ems/site/zdlb/MonitorPointMapping.vue'),
name: 'MonitorPointMapping', name: 'MonitorPointMapping',
meta: { title: '单站监控项目点位配置', activeMenu: '/ems/site/zdlb' } meta: { title: '单站监控项目点位配置', activeMenu: '/dzjk/clpz/sbbh' }
} }
] ]
}, },

View File

@ -50,4 +50,3 @@ export default {
flex: 1; flex: 1;
} }
</style> </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-form-item label="soc限制" prop="sdcLimit" required>
<el-switch :active-value="1" :inactive-value="0" v-model="formData.sdcLimit"></el-switch> <el-switch :active-value="1" :inactive-value="0" v-model="formData.sdcLimit"></el-switch>
</el-form-item> </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-form>
<el-button type="primary" size="mini" @click="addTime">新增</el-button> <el-button type="primary" size="mini" @click="addTime">新增</el-button>
<!-- 新增时间段表单--> <!-- 新增时间段表单-->
@ -64,6 +56,12 @@
<el-input v-model="formInline.chargeDischargePower" placeholder="请输入" <el-input v-model="formInline.chargeDischargePower" placeholder="请输入"
:style="{width: '100%'}"></el-input> :style="{width: '100%'}"></el-input>
</el-form-item> </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-form-item label="充电状态" prop="chargeStatus">
<el-select v-model="formInline.chargeStatus" placeholder="请选择" :style="{width: '100%'}"> <el-select v-model="formInline.chargeStatus" placeholder="请选择" :style="{width: '100%'}">
<el-option v-for="(value,key) in chargeStatusOptions" :key="key+'chargeStatusOptions'" :label="value" <el-option v-for="(value,key) in chargeStatusOptions" :key="key+'chargeStatusOptions'" :label="value"
@ -84,20 +82,100 @@
<el-table-column <el-table-column
prop="startTime" prop="startTime"
label="开始时间"> 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>
<el-table-column <el-table-column
prop="endTime" prop="endTime"
label="结束时间"> 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>
<el-table-column <el-table-column
prop="chargeDischargePower" prop="chargeDischargePower"
label="充放功率kW"> 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>
<el-table-column <el-table-column
prop="chargeStatus" prop="chargeStatus"
label="充电状态"> label="充电状态">
<template slot-scope="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -138,8 +216,6 @@ export default {
formData: { formData: {
templateName: '', templateName: '',
sdcLimit: false, sdcLimit: false,
sdcDown: '',
sdcUp: '',
}, },
rules: { rules: {
templateName: [{ templateName: [{
@ -147,19 +223,13 @@ export default {
message: '请输入', message: '请输入',
trigger: 'blur' 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, showAddTime: false,
formInline: { formInline: {
timeRange: range, timeRange: range,
chargeDischargePower: '', chargeDischargePower: '',
sdcDown: '',
sdcUp: '',
chargeStatus: '' chargeStatus: ''
}, },
formInlineRule: { formInlineRule: {
@ -175,6 +245,14 @@ export default {
}, },
{pattern: /^-?\d*\.?\d*$/, message: '请输入合法数字或小数'} {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: [{ chargeStatus: [{
required: true, required: true,
message: '请选择充放状态', message: '请选择充放状态',
@ -198,11 +276,9 @@ export default {
this.formData = { this.formData = {
templateName: '', templateName: '',
sdcLimit: false, sdcLimit: false,
sdcDown: '',
sdcUp: '',
} }
this.formInline = { this.formInline = {
timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: '' timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''
}//startTime: '', endTime: '', }//startTime: '', endTime: '',
this.showAddTime = false this.showAddTime = false
this.tableData = [] this.tableData = []
@ -216,14 +292,12 @@ export default {
getStrategyTempDetail(this.editTempId).then(response => { getStrategyTempDetail(this.editTempId).then(response => {
const data = JSON.parse(JSON.stringify(response?.data || [])); const data = JSON.parse(JSON.stringify(response?.data || []));
if (data.length > 0) { 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.templateName = templateName
this.formData.sdcLimit = sdcLimit this.formData.sdcLimit = sdcLimit
this.formData.sdcDown = sdcDown
this.formData.sdcUp = sdcUp
} }
if (data.length === 1) { if (data.length === 1) {
const {startTime, endTime} = data; const {startTime, endTime} = data[0];
if (!startTime || !endTime) { if (!startTime || !endTime) {
this.tableData = [] this.tableData = []
} else { } else {
@ -242,15 +316,15 @@ export default {
cancelAddTime() { cancelAddTime() {
this.$refs.addTimeForm.resetFields() this.$refs.addTimeForm.resetFields()
this.showAddTime = false this.showAddTime = false
this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''}//startTime: '', endTime: '', this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''}//startTime: '', endTime: '',
}, },
saveTime() { saveTime() {
//表单校验校验成功添加到tableData里 //表单校验校验成功添加到tableData里
this.$refs.addTimeForm.validate(valid => { this.$refs.addTimeForm.validate(valid => {
if (!valid) return 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.$nextTick(() => {
this.cancelAddTime() this.cancelAddTime()
}) })
@ -262,9 +336,14 @@ export default {
saveDialog() { saveDialog() {
this.$refs.addTempForm.validate(valid => { this.$refs.addTempForm.validate(valid => {
if (!valid) return if (!valid) return
const {templateName, sdcLimit, sdcDown, sdcUp} = this.formData const {templateName, sdcLimit} = this.formData
const {siteId, updateStrategyId} = this.$home 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') { if (this.mode === 'edit') {
editStrategyTemp({ editStrategyTemp({
siteId, siteId,
@ -272,8 +351,6 @@ export default {
templateId: this.editTempId, templateId: this.editTempId,
templateName, templateName,
sdcLimit, sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData timeConfigList: tableData
}).then(response => { }).then(response => {
if (response?.code === 200) { if (response?.code === 200) {
@ -288,8 +365,6 @@ export default {
strategyId: updateStrategyId, strategyId: updateStrategyId,
templateName, templateName,
sdcLimit, sdcLimit,
sdcDown,
sdcUp,
timeConfigList: tableData timeConfigList: tableData
}).then(response => { }).then(response => {
if (response?.code === 200) { 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() { closeDialog() {
// 清空所有数据 // 清空所有数据
this.$refs.addTempForm.resetFields() this.$refs.addTempForm.resetFields()
this.formData = { this.formData = {
templateName: '', templateName: '',
sdcLimit: 0, sdcLimit: 0,
sdcDown: '',
sdcUp: '',
} }
this.tableData = [] this.tableData = []
this.cancelAddTime() this.cancelAddTime()
@ -322,4 +447,4 @@ export default {
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

View File

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

View File

@ -12,7 +12,8 @@
import * as echarts from 'echarts' import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize' import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue' 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"; import intervalUpdate from "@/mixins/ems/intervalUpdate";
export default { export default {
@ -48,8 +49,34 @@ export default {
getGVQXData() { getGVQXData() {
this.showLoading() this.showLoading()
const {siteId, timeRange} = this const {siteId, timeRange} = this
getPointData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => { getProjectDisplayData(siteId).then(response => {
this.setOption(response?.data || []) 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()) }).finally(() => this.hideLoading())
}, },
init(siteId) { init(siteId) {
@ -70,12 +97,18 @@ export default {
hideLoading() { hideLoading() {
this.chart && this.chart.hideLoading() this.chart && this.chart.hideLoading()
}, },
setOption(data) { normalizeDateTime(value, endOfDay) {
const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率', 'soc平均值', 'soh平均值', '电池平均温度平均值']] const raw = String(value || '').trim()
console.log('source.slice(1)', source[0].slice(1)) if (!raw) return ''
this.chart && data.forEach((item) => { if (raw.includes(' ')) return raw
source.push([item.statisDate, item.gridPower, item.loadPower, item.storagePower, item.pvPower, item.avgSoc, item.avgSoh, item.avgTemp]) 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({ this.chart.setOption({
grid: { grid: {
containLabel: true containLabel: true
@ -86,35 +119,28 @@ export default {
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效 axisPointer: { type: 'cross' }
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
}, },
textStyle: { textStyle: {
color: "#333333", color: "#333333",
}, },
xAxis: { xAxis: {
type: 'category', type: 'time',
}, },
yAxis: [ yAxis: [{
{ type: 'value',
type: 'value', }],
}, series: seriesData.map((item) => {
{
type: 'value',
},
],
dataset: {source},
series: source[0].slice(1).map((item, index) => {
return { return {
type: 'line',//index === 5 ? 'bar' : 'line', name: item.name,
type: 'line',
showSymbol: false, showSymbol: false,
symbolSize: 2, symbolSize: 2,
smooth: true, smooth: true,
areaStyle: { areaStyle: {
opacity: 0.5, opacity: 0.5,
}, },
yAxisIndex: index <= 4 ? 0 : 1 data: item.data
} }
}) })
}) })
@ -124,4 +150,3 @@ export default {
} }
</script> </script>

View File

@ -12,7 +12,8 @@
import * as echarts from 'echarts' import * as echarts from 'echarts'
import resize from '@/mixins/ems/resize' import resize from '@/mixins/ems/resize'
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue' 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 { export default {
mixins: [resize], mixins: [resize],
@ -45,8 +46,34 @@ export default {
getWeekKData() { getWeekKData() {
this.showLoading() this.showLoading()
const {siteId, timeRange} = this const {siteId, timeRange} = this
getSevenChargeData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => { getProjectDisplayData(siteId).then(response => {
this.setOption(response?.data || []) 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()) }).finally(() => this.hideLoading())
}, },
init(siteId) { init(siteId) {
@ -65,18 +92,23 @@ export default {
hideLoading() { hideLoading() {
this.chart && this.chart.hideLoading() this.chart && this.chart.hideLoading()
}, },
setOption(data, unit) { normalizeDateTime(value, endOfDay) {
const source = [['日期', '充电量', '放电量']] const raw = String(value || '').trim()
data.forEach(item => { if (!raw) return ''
source.push([item.ammeterDate, item.chargedCap, item.disChargedCap]) 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({ this.chart && this.chart.setOption({
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一 color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效 axisPointer: { type: 'cross' }
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
}, },
grid: { grid: {
containLabel: true containLabel: true
@ -86,9 +118,7 @@ export default {
bottom: '15', bottom: '15',
}, },
xAxis: { xAxis: {
type: 'category', type: 'time'
name: unit,
nameLocation: 'center'
}, },
yAxis: [{ yAxis: [{
type: 'value', type: 'value',
@ -100,19 +130,12 @@ export default {
onZero: false onZero: false
} }
}], }],
dataset: { series: seriesData.map(item => ({
source name: item.name,
}, yAxisIndex: 0,
series: [ type: 'bar',
{ data: item.data
yAxisIndex: 0, }))
type: 'bar',
},
{
yAxisIndex: 0,
type: 'bar',
},
]
}) })
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,11 +1,6 @@
<template> <template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading"> <div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container"> <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-form-item label="订阅topic">
<el-input <el-input
v-model="form.mqttTopic" v-model="form.mqttTopic"
@ -90,12 +85,21 @@
<script> <script>
import {deleteMqtt,getMqttList} from '@/api/ems/site' import {deleteMqtt,getMqttList} from '@/api/ems/site'
import {getAllSites} from '@/api/ems/zddt'
import AddMqtt from './AddMqtt.vue' import AddMqtt from './AddMqtt.vue'
export default { export default {
name: "Mqtt", name: "Mqtt",
components: {AddMqtt}, components: {AddMqtt},
computed: { }, 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() { data() {
return { return {
form:{ form:{
@ -103,8 +107,6 @@ export default {
topicName:'', topicName:'',
mqttTopic:'' mqttTopic:''
}, },
siteList:[],
searchLoading:false,
loading:false, loading:false,
tableData:[], tableData:[],
pageSize:10,//分页栏当前每个数据总数 pageSize:10,//分页栏当前每个数据总数
@ -113,6 +115,9 @@ export default {
} }
}, },
methods:{ methods:{
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
// 分页 // 分页
handleSizeChange(val) { handleSizeChange(val) {
this.pageSize = val; this.pageSize = val;
@ -133,22 +138,19 @@ export default {
}, },
onReset(){ onReset(){
this.form={ this.form={
siteId:'', siteId:this.form.siteId,
topicName:'', topicName:'',
mqttTopic:'' mqttTopic:''
} }
this.pageNum =1//每次搜索从1开始搜索 this.pageNum =1//每次搜索从1开始搜索
this.getData() 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(){ getData(){
if (!this.form.siteId) {
this.tableData = []
this.totalSize = 0
return
}
this.loading=true; this.loading=true;
const {mqttTopic,topicName,siteId} = this.form; const {mqttTopic,topicName,siteId} = this.form;
const {pageNum,pageSize} = this; const {pageNum,pageSize} = this;
@ -193,8 +195,7 @@ export default {
}, },
}, },
mounted() { mounted() {
this.loading=true this.form.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.getZdList()
this.getData() this.getData()
} }
} }

View File

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

View File

@ -1,11 +1,6 @@
<template> <template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading"> <div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container"> <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-form-item label="年份选择">
<el-date-picker <el-date-picker
v-model="defaultYear" v-model="defaultYear"
@ -33,7 +28,7 @@
:key="item.id" :key="item.id"
> >
<div slot="header" class="time-range-header"> <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> <div>
<el-button type="primary" size="mini" @click="addPowerConfig(item.id)">编辑</el-button> <el-button type="primary" size="mini" @click="addPowerConfig(item.id)">编辑</el-button>
<el-button type="warning" size="mini" @click="deletePowerConfig(item)">删除</el-button> <el-button type="warning" size="mini" @click="deletePowerConfig(item)">删除</el-button>
@ -61,21 +56,28 @@
<script> <script>
import {energyPriceConfig,listPriceConfig} from '@/api/ems/powerTariff' import {energyPriceConfig,listPriceConfig} from '@/api/ems/powerTariff'
import {getAllSites} from '@/api/ems/zddt'
import AddPowerTariff from './AddPowerTariff.vue' import AddPowerTariff from './AddPowerTariff.vue'
import DateTimeSelect from "@/views/ems/search/DateTimeSelect.vue"; import DateTimeSelect from "@/views/ems/search/DateTimeSelect.vue";
export default { export default {
name: "PowerTariff", name: "PowerTariff",
components: {DateTimeSelect, AddPowerTariff}, components: {DateTimeSelect, AddPowerTariff},
computed: { }, 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() { data() {
return { return {
loading:false, loading:false,
pageNum:1, pageNum:1,
pageSize:40, pageSize:40,
searchLoading:false,
siteId:'', siteId:'',
siteList:[],
tableData:[], tableData:[],
tableTotal:0, tableTotal:0,
defaultYear:'', defaultYear:'',
@ -93,6 +95,9 @@ export default {
} }
}, },
methods:{ methods:{
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
resetTableData(){ resetTableData(){
this.tableData=[] this.tableData=[]
this.tableTotal=0 this.tableTotal=0
@ -102,19 +107,16 @@ export default {
onSearch(){ onSearch(){
this.getData(true) 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(){ changeDefaultYear(){
this.getData(true) this.getData(true)
}, },
getData(reset=false){ getData(reset=false){
reset && this.resetTableData() reset && this.resetTableData()
if (!this.siteId) {
this.tableData = []
this.tableTotal = 0
return
}
if(!reset && this.tableData.length>=this.tableTotal) return if(!reset && this.tableData.length>=this.tableTotal) return
this.loading=true; this.loading=true;
const date = new Date(this.defaultYear).getFullYear() const date = new Date(this.defaultYear).getFullYear()
@ -163,10 +165,8 @@ export default {
}, },
mounted() { mounted() {
this.defaultYear = new Date() this.defaultYear = new Date()
this.loading=true this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.getZdList().then(()=>{ this.getData(true)
this.getData(true)
})
} }
} }
</script> </script>

View File

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

View File

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

View File

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

View File

@ -1,13 +1,6 @@
<template> <template>
<div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading"> <div class="ems-dashboard-editor-container" style="background-color: #ffffff" v-loading="loading">
<el-form :inline="true" class="select-container"> <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-form-item label="设备类型">
<el-select v-model="deviceCategory" placeholder="请选择设备类型" @change="onSearch" clearable> <el-select v-model="deviceCategory" placeholder="请选择设备类型" @change="onSearch" clearable>
<el-option <el-option
@ -24,34 +17,6 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-button type="primary" @click="addDevice" native-type="button">新增设备</el-button> <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 <el-table
class="common-table" class="common-table"
:data="tableData" :data="tableData"
@ -79,39 +44,11 @@
prop="categoryName" prop="categoryName"
label="设备类别"> label="设备类别">
</el-table-column> </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 <el-table-column
fixed="right" fixed="right"
label="操作" label="操作"
width="250"> width="180">
<template slot-scope="scope"> <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 <el-button
@click="editDevice(scope.row)" @click="editDevice(scope.row)"
style="margin-top:10px;" style="margin-top:10px;"
@ -154,30 +91,34 @@
</div> </div>
</el-dialog> </el-dialog>
<add-device ref="addDevice" :mode="mode" :id="editDeviceId" @update="getData" @clear="clearEditDeviceData"/> <add-device ref="addDevice" :mode="mode" :id="editDeviceId" @update="getData" @clear="clearEditDeviceData"/>
<point-upload ref="pointUpload" @update="getData"/>
</div> </div>
</template> </template>
<script> <script>
import {deleteService, getDeviceDetailInfo, getDeviceInfoList} from '@/api/ems/site' import {deleteService, getDeviceDetailInfo, getDeviceInfoList} from '@/api/ems/site'
import {getAllSites} from '@/api/ems/zddt'
import {formatNumber} from "@/filters/ems"; import {formatNumber} from "@/filters/ems";
import {getAllDeviceCategory} from '@/api/ems/search' import {getAllDeviceCategory} from '@/api/ems/search'
import AddDevice from "./AddDevice.vue"; import AddDevice from "./AddDevice.vue";
import PointUpload from "./PointUpload.vue";
// import PcsSwitch from "./PcsSwitch.vue";
export default { export default {
name: "Sblb", 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() { data() {
return { return {
loading: false, loading: false,
searchLoading: false,
mode: '',//新增、编辑设备 mode: '',//新增、编辑设备
editDeviceId: '',//编辑设备id editDeviceId: '',//编辑设备id
siteId: '', siteId: '',
siteList: [],
deviceCategory: '',//搜索栏设备类型 deviceCategory: '',//搜索栏设备类型
deviceCategoryList: [],//设备类别 deviceCategoryList: [],//设备类别
tableData: [], tableData: [],
@ -209,34 +150,15 @@ export default {
} }
}, },
methods: { methods: {
hasValidSiteId(siteId) {
return !!(siteId !== undefined && siteId !== null && String(siteId).trim())
},
// 获取设备类别 // 获取设备类别
getDeviceCategoryList() { getDeviceCategoryList() {
getAllDeviceCategory().then(response => { getAllDeviceCategory().then(response => {
this.deviceCategoryList = response?.data || [] 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() { clearEditDeviceData() {
this.mode = ''; this.mode = '';
this.editDeviceId = '' this.editDeviceId = ''
@ -342,26 +264,13 @@ export default {
}).finally(() => { }).finally(() => {
this.loading = false 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() { mounted() {
this.loading = true this.siteId = this.hasValidSiteId(this.$route.query.siteId) ? String(this.$route.query.siteId).trim() : ''
this.siteId = ''
this.pageNum = 1//每次搜索从1开始搜索 this.pageNum = 1//每次搜索从1开始搜索
this.getDeviceCategoryList() this.getDeviceCategoryList()
this.getZdList().then(() => { this.getData()
this.getData()
})
} }
} }
</script> </script>

View File

@ -3,7 +3,10 @@
<div class="header-row"> <div class="header-row">
<div class="title-block"> <div class="title-block">
<div class="page-title">单站监控项目点位配置</div> <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>
<div> <div>
<el-button @click="goBack">返回</el-button> <el-button @click="goBack">返回</el-button>
@ -34,15 +37,42 @@
<el-table-column prop="section" label="分组" min-width="180" /> <el-table-column prop="section" label="分组" min-width="180" />
<el-table-column prop="name" label="页面展示名称" min-width="280" /> <el-table-column prop="name" label="页面展示名称" min-width="280" />
<el-table-column prop="field" label="字段名" min-width="260" /> <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"> <template slot-scope="scope">
<el-input <el-input
v-model.trim="scope.row.point" v-model.trim="scope.row.point"
placeholder="请输入点位" placeholder="请输入点位"
clearable clearable
class="short-input"
/> />
</template> </template>
</el-table-column> </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-table>
<el-tabs <el-tabs
@ -61,15 +91,42 @@
<el-table-column prop="section" label="分组" min-width="180" /> <el-table-column prop="section" label="分组" min-width="180" />
<el-table-column prop="name" label="页面展示名称" min-width="280" /> <el-table-column prop="name" label="页面展示名称" min-width="280" />
<el-table-column prop="field" label="字段名" min-width="260" /> <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"> <template slot-scope="scope">
<el-input <el-input
v-model.trim="scope.row.point" v-model.trim="scope.row.point"
placeholder="请输入点位" placeholder="请输入点位"
clearable clearable
class="short-input"
/> />
</template> </template>
</el-table-column> </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-table>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -85,17 +142,25 @@ export default {
name: 'MonitorPointMapping', name: 'MonitorPointMapping',
data() { data() {
return { return {
siteId: '',
siteName: '',
activeTopMenu: 'HOME', activeTopMenu: 'HOME',
activeChildMap: {}, activeChildMap: {},
topMenuList: [] topMenuList: [],
deletedFieldCodes: []
} }
}, },
computed: { watch: {
siteId() { '$route.query.siteId': {
return this.$route.query.siteId || '' immediate: false,
}, async handler(newSiteId) {
siteName() { if (newSiteId === this.siteId) {
return this.$route.query.siteName || '' return
}
this.siteId = newSiteId || ''
this.siteName = this.$route.query.siteName || this.siteId
await this.initMenuStructure()
}
} }
}, },
methods: { methods: {
@ -105,6 +170,7 @@ export default {
async initMenuStructure() { async initMenuStructure() {
if (!this.siteId) { if (!this.siteId) {
this.topMenuList = [] this.topMenuList = []
this.deletedFieldCodes = []
return return
} }
const response = await getSingleMonitorProjectPointMapping(this.siteId) const response = await getSingleMonitorProjectPointMapping(this.siteId)
@ -131,7 +197,9 @@ export default {
section: row.sectionName || '-', section: row.sectionName || '-',
name: row.fieldName || '-', name: row.fieldName || '-',
field: row.fieldCode || '', field: row.fieldCode || '',
point: row.dataPoint || '' point: row.dataPoint || '',
fixedValue: row.fixedDataPoint || '',
useFixedDisplay: row.useFixedDisplay === 1 ? 1 : 0
} }
const topItem = topMap.get(topKey) const topItem = topMap.get(topKey)
const isTopDirect = moduleCode === menuCode || moduleCode === 'HOME' const isTopDirect = moduleCode === menuCode || moduleCode === 'HOME'
@ -152,6 +220,7 @@ export default {
const firstTop = this.topMenuList[0] const firstTop = this.topMenuList[0]
this.activeTopMenu = firstTop ? firstTop.code : 'HOME' this.activeTopMenu = firstTop ? firstTop.code : 'HOME'
this.activeChildMap = {} this.activeChildMap = {}
this.deletedFieldCodes = []
this.topMenuList.forEach(top => { this.topMenuList.forEach(top => {
if (top.children && top.children.length > 0) { if (top.children && top.children.length > 0) {
this.$set(this.activeChildMap, top.code, top.children[0].code) this.$set(this.activeChildMap, top.code, top.children[0].code)
@ -168,6 +237,23 @@ export default {
}) })
return rows 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() { async saveData() {
if (!this.siteId) { if (!this.siteId) {
this.$message.warning('缺少站点ID') this.$message.warning('缺少站点ID')
@ -175,13 +261,29 @@ export default {
} }
const mappings = this.getAllMappingRows() const mappings = this.getAllMappingRows()
.filter(item => item.field) .filter(item => item.field)
.map(item => ({ fieldCode: item.field, dataPoint: item.point || '' })) .map(item => ({
await saveSingleMonitorProjectPointMapping({ siteId: this.siteId, mappings }) fieldCode: item.field,
this.$message.success('保存成功') 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() await this.initMenuStructure()
} }
}, },
async mounted() { async mounted() {
this.siteId = this.$route.query.siteId || ''
this.siteName = this.$route.query.siteName || this.siteId
await this.initMenuStructure() await this.initMenuStructure()
} }
} }
@ -209,6 +311,16 @@ export default {
margin-top: 6px; margin-top: 6px;
font-size: 13px; font-size: 13px;
color: #909399; color: #909399;
display: flex;
align-items: center;
}
.site-label {
margin-right: 8px;
}
.short-input {
width: 220px;
} }
.child-menu-tabs { .child-menu-tabs {

View File

@ -59,11 +59,10 @@
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="操作" label="操作"
width="220" width="120"
fixed="right"> fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="small" @click="openEditDialog(scope.row)">编辑</el-button> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -266,15 +265,6 @@ export default {
this.$refs.siteForm && this.$refs.siteForm.clearValidate() 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() { submitSite() {
this.$refs.siteForm.validate(valid => { this.$refs.siteForm.validate(valid => {
if (!valid) { if (!valid) {