feat(web): 新增预测管理和策略表单功能

- 添加预测管理页面和相关组件
- 实现策略表单组件,支持创建和编辑策略
- 优化表格组件,增加分页和数据加载功能
- 调整视频监控组件布局
- 修复部分组件样式问题
This commit is contained in:
zhoumengru
2025-09-04 13:42:48 +08:00
parent 6d6d05e18f
commit 5f5eeb1cbf
22 changed files with 1548 additions and 312 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -26,5 +26,6 @@ const locale = ref(zhCN)
right: 0; right: 0;
min-width: 1440px; min-width: 1440px;
min-height: 900px; min-height: 900px;
color: #fff;
} }
</style> </style>

View File

@@ -39,13 +39,13 @@
:data-source="record.currentLimitList" :data-source="record.currentLimitList"
:pagination="false" :pagination="false"
> >
<template #bodyCell="{ column, record_ }"> <!-- <template #bodyCell="{ column, record_ }">
<template v-if="column.key === 'type'"> <template v-if="column.key === 'type'">
<span> <span>
{{ getType(record_.type) }} {{ getType(record_.type) }}
</span> </span>
</template> </template>
</template> </template> -->
</a-table> </a-table>
</template> </template>
</a-table> </a-table>

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="search"> <div class="search">
<div class="top" v-if="searchOptions.length"> <div class="top" v-if="searchOptions.length">
<div class="top-left"> <div class="top-left">
<template v-for="item in searchOptions" :key="item.key"> <template v-for="item in searchOptions" :key="item.key">
@@ -9,9 +8,10 @@
<span class="label"> {{ item.label }}</span> <span class="label"> {{ item.label }}</span>
<div class="select" v-if="item.type == 'select'"> <div class="select" v-if="item.type == 'select'">
<a-select <a-select
:dropdown-match-select-width="false" v-model:value="formData[item.key]" :dropdown-match-select-width="false"
v-model:value="formData[item.key]"
allow-clear allow-clear
:max-tag-count='2' :max-tag-count="2"
:placeholder="item.label" :placeholder="item.label"
:mode="item.mode ? item.mode : 'combobox'" :mode="item.mode ? item.mode : 'combobox'"
> >
@@ -70,12 +70,11 @@
<div style="display: flex" v-if="btnOptionList.length"> <div style="display: flex" v-if="btnOptionList.length">
<div v-for="(item, i) in btnOptionList" :key="i" class="button"> <div v-for="(item, i) in btnOptionList" :key="i" class="button">
<a-button <a-button
:class="'btn-' + item.type" :class="'btn-' + item.type"
type="primary" type="primary"
@click="handelClick(item.type)" @click="handelClick(item.type)"
:disabled="item.disabled ? item.disabled : false" :disabled="item.disabled ? item.disabled : false"
> >
{{ item.label }} {{ item.label }}
</a-button> </a-button>
</div> </div>
@@ -188,7 +187,7 @@ input:-internal-autofill-selected {
color: var(--theme-text-default) !important; color: var(--theme-text-default) !important;
} }
.search { .search {
color:#fff; color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@@ -232,12 +231,12 @@ input:-internal-autofill-selected {
.date-picker { .date-picker {
width: 360px; width: 360px;
.ant-picker{ .ant-picker {
background: transparent; background: transparent;
border: 1px solid #00B9D0; border: 1px solid #00b9d0;
.ant-picker-input >input{ .ant-picker-input > input {
color: #fff!important; color: #fff !important;
} }
} }
} }
@@ -323,19 +322,13 @@ input:-internal-autofill-selected {
} }
.bottom { .bottom {
display: flex; display: flex;
margin-top: 15px; margin-top: 20px;
margin-bottom: 15px; margin-bottom: 20px;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.button{ .button {
margin-left: 10px; margin-left: 10px;
} }
}
} }
}
</style> </style>

View File

@@ -1,8 +1,8 @@
<template> <template>
<div class="device"> <div class="device">
<div class="device-item" v-for="item in 8" :key="item"> <div class="device-item" v-for="item in deviceList" :key="item">
<div class="item-header"> <div class="item-header">
<div style="display: flex;width: 50%;"> <div style="display: flex; width: 50%">
<div class="icon-bg"></div> <div class="icon-bg"></div>
<div class="title"> <div class="title">
<span class="number">521245786665412</span> <span class="number">521245786665412</span>
@@ -29,12 +29,18 @@
<div class="item-content"> <div class="item-content">
<div v-for="info in chunengInfo" :key="info.key"> <div v-for="info in chunengInfo" :key="info.key">
<span class="text">{{ info.label }}</span> <span class="text">{{ info.label }}</span>
<a-button v-if="info.key === 'realTimeData'" type="primary" size="small" @click="openModal">查看</a-button> <a-button
<span v-else class="value" >{{ info.value }}</span> v-if="info.key === 'realTimeData'"
type="primary"
size="small"
@click="openModal"
>查看</a-button
>
<span v-else class="value">{{ info.value }}</span>
</div> </div>
</div> </div>
</div> </div>
<a-modal v-model:open="modalOpen" @ok="handleOk" width="800px"> <a-modal v-model:open="modalOpen" @ok="handleOk" width="800px">
<!-- <p>Some contents...</p> <!-- <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> --> <p>Some contents...</p> -->
@@ -43,16 +49,27 @@
</template> </template>
<script> <script>
import { postReq, getReq } from '@/request/api'
export default { export default {
name: '', name: '',
components: {}, components: {},
props: {}, props: {
stationId: {
type: String,
default: ''
},
systemType: {
type: Number,
default: 1
}
},
data() { data() {
return { return {
modalOpen:false, modalOpen: false,
deviceList: [],
chunengInfo: [ chunengInfo: [
{label:'运行模式',key:'operationMode',value:'并网运行'}, { label: '运行模式', key: 'operationMode', value: '并网运行' },
{label:'电池储能容量',key:'batteryCapacity',value:'100kWh'}, { label: '电池储能容量', key: 'batteryCapacity', value: '100kWh' },
{ label: '实时电压', key: 'voltage', value: '232.5V' }, { label: '实时电压', key: 'voltage', value: '232.5V' },
{ label: '功率因数', key: 'powerFactor', value: '0.95' }, { label: '功率因数', key: 'powerFactor', value: '0.95' },
{ label: '实时电流', key: 'current', value: '0.01A' }, { label: '实时电流', key: 'current', value: '0.01A' },
@@ -62,26 +79,48 @@ export default {
{ label: '实时数据', key: 'realTimeData', value: '0.01kWh' }, { label: '实时数据', key: 'realTimeData', value: '0.01kWh' },
{ label: '额定功率', key: 'ratedPower', value: '0.01kW' }, { label: '额定功率', key: 'ratedPower', value: '0.01kW' },
{ label: '冷却方式', key: 'coolingMethod', value: '风冷' } { label: '冷却方式', key: 'coolingMethod', value: '风冷' }
]
],
// guangfuInfo: [
// { label: '实时电压', key: 'voltage', value: '232.5V' },
// { label: '额定电压', key: 'ratedVoltage', value: '232.5V' },
// { label: '实时电流', key: 'current', value: '0.01A' },
// { label: '额定电流', key: 'ratedCurrent', value: '0.01A' },
// { label: '实时功率', key: 'power', value: '0.01kW' },
// { label: '额定功率', key: 'ratedPower', value: '0.01kW' },
// { label: '实时数据', key: 'realTimeData', value: '0.01kWh' }
// ]
} }
}, },
mounted() {}, watch: {
methods: { // 监听父组件数据变化
openModal(){ stationId(newVal) {
this.modalOpen=true; if (newVal) {
this.getDeviceList()
}
}, },
handleOk(){ systemType(newVal, oldVal) {
this.modalOpen=false; if (newVal !== oldVal) {
this.getDeviceList()
}
}
},
mounted() {
this.getDeviceList()
},
methods: {
async getDeviceList() {
const data = {
category: this.systemType,
'station_id': this.stationId,
page: 0,
'page_size': 1000
}
try {
const res = await getReq('/queryDeviceList', data)
console.log(res)
this.deviceList = res.data
// this.selectStation=this.stations[0]['station_id']
} catch (error) {
console.log(error)
}
},
openModal() {
this.modalOpen = true
},
handleOk() {
this.modalOpen = false
} }
} }
} }

View File

@@ -21,35 +21,26 @@
</div> </div>
</div> </div>
<div class="environment"> <div class="environment">
<div class="item"> <div class="tab-header">
<div class="title">环境温湿度信息</div> <div v-for="item in tabList" :key="item.key" class="tab">
<img src="@/assets/images/titleLine.png" alt="" width="100%" /> <span
<div class="content"> :class="[activeTab == item.key ? 'actived' : 'uactived']"
<div class="header"> @click="activeTab = item.key"
<div>点位</div> >{{ item.name }}</span
<div>温度</div> >
<div>湿度</div>
</div>
<div class="row">
<div>#1</div>
<div>20 </div>
<div>20%</div>
</div>
</div> </div>
</div> </div>
<div class="item" style="margin-top: 40px"> <div class="table-content">
<div class="title">消防信息</div> <ComTable
<img src="@/assets/images/titleLine.png" alt="" width="100%" /> :columns="columns"
<div class="content"> :table-data="tableData"
<div class="header"> @handlePagesizeChange="handlePagesizeChange"
<div>点位</div> ref="comTable"
<div>烟感状态</div> :table-option="tableOption"
</div> :page-option="pageOption"
<div class="row" v-for="value in 6" :key="value"> :table-h="tableH"
<div>#{{ value }}</div> >
<div>xxx</div> </ComTable>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -60,7 +51,51 @@ export default {
components: {}, components: {},
props: {}, props: {},
data() { data() {
return {} return {
activeTab: '0',
tabList: [
{
key: '0',
name: '环境温湿度信息'
},
{
key: '1',
name: '安防信息'
},
{
key: '2',
name: '空调信息'
},
{
key: '3',
name: '冷机信息'
}
],
columns: [
{
title: '点位',
dataIndex: 'policyId',
key: 'policyId',
ellipsis: true
},
{
title: '温度',
dataIndex: 'name',
key: 'name',
ellipsis: true
},
{
title: '湿度',
dataIndex: 'type',
key: 'type',
ellipsis: true
}
],
tableOption: {
select: false,
page: false
}
}
}, },
mounted() {}, mounted() {},
methods: {} methods: {}
@@ -69,11 +104,11 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.videos { .videos {
width: calc(100% - 240px); width: 60%;
margin-left: 20px; margin-left: 20px;
display: grid; display: grid;
grid-gap: 20px; grid-gap: 20px;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr;
overflow-y: auto; overflow-y: auto;
.video-item { .video-item {
// width: 410px; // width: 410px;
@@ -95,9 +130,10 @@ export default {
} }
} }
.environment { .environment {
width: 220px; width: calc(40% - 20px);
margin-left: 10px; margin-left: 20px;
color: #fff; color: #fff;
.title { .title {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
@@ -125,5 +161,39 @@ export default {
} }
} }
} }
.tab-header {
display: flex;
.tab {
& > span {
font-size: 14px;
margin-right: 15px;
display: inline-block;
padding: 10px;
cursor: pointer;
border: 1px solid $tab-border;
border-radius: 4px;
}
}
.actived {
color: #ffffff;
background-color: $green;
}
.uactived {
color: #a6b8dd;
background-color: $bg2-color;
}
}
.table-content {
margin-top: 20px;
:deep(.ant-table) {
border-radius: 10px 10px 0 0 !important;
overflow: hidden; /* 确保圆角生效 */
}
:deep(.ant-table-body) {
border-radius: 0px 0px 10px 10px !important;
}
}
} }
</style> </style>

View File

@@ -0,0 +1,187 @@
<template>
<div class="echarts">
<div class="chart-container">
<div class="content-header">
<div class="verline"></div>
<span>{{ chartOptions.title }}</span>
</div>
<div ref="chartContainer" class="echarts-content"></div>
</div>
</div>
</template>
<script>
import { postReq } from '@/request/api'
export default {
name: 'PredictEcharts',
props: {
chartOptions: {
type: Object,
default: () => {}, // 默认空对象
required: false // 非必须
},
chartData: {
type: Array,
default: () => ([]), // 默认空对象
required: false // 非必须
}
},
data() {
return {
chartInstances: [] // 存储 ECharts 实例
}
},
mounted() {
this.$nextTick(() => {
// 确保 DOM 完全渲染
this.initCharts()
window.addEventListener('resize', this.handleResize)
})
},
beforeUnmount() {
window.removeEventListener('resize', this.handleResize)
this.chartInstances.forEach((chart) => chart && chart.dispose())
},
methods: {
initCharts() {
// this.chartOptions.forEach((option, index) => {
const {title,infoKeys,dataKey,type,smooth}=this.chartOptions
const dom = this.$refs.chartContainer
if (!dom) return
const chart = this.$echarts.init(dom)
this.chartInstances.push(chart) // 存储实例
// 设置图表配置
chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
top: 20,
textStyle: {
color: '#fff'
},
data: infoKeys.map((info) => info.label)
},
grid: {
left: '3%',
right: '3%',
bottom: '1%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.chartData.map((item) => item.date),
axisLine: {
show: true,
lineStyle: {
color: '#fff' // x轴线颜色
}
},
axisLabel: {
color: '#fff'
},
axisTick: {
lineStyle: {
color: '#fff' // x 轴刻度线颜色
}
},
},
yAxis: {
type: 'value',
axisLine: {
show: true,
lineStyle: {
color: '#fff' // y轴线颜色
}
},
axisLabel: {
color: '#fff'
},
splitLine: {
lineStyle: {
color: '#A6B8DD', // 网格线颜色,
type: 'dashed'
}
}
},
series: infoKeys.map((info, i) => {
return {
name: info.label,
smooth: smooth || false,
type: type,
data: this.chartData.map((item) => item[info.key]),
...info.seriesOptions,
}
})
})
// })
},
handleResize() {
this.chartInstances.forEach((chart) => chart && chart.resize())
}
}
}
</script>
<style lang="scss" scoped>
.content {
height: 100%;
width: 100%;
}
.echarts {
display: flex;
height: 100%;
width: 100%;
color: #fff;
.chart-container {
width: 100%;
height: 100%;
}
& > div {
width: 100%;
height: 100%;
.content-header {
height: 20px;
font-size: 12px;
display: flex;
align-items: center;
margin-left: 3%;
.verline {
margin-right: 10px;
background: linear-gradient(
180deg,
rgba(0, 230, 172, 1) 0%,
rgba(0, 210, 255, 1) 98.78%,
rgba(0, 210, 255, 1) 100%
),
rgba(1, 223, 239, 1);
width: 4px;
height: 20px;
border-radius: 6px;
}
}
.echarts-content{
height: calc(100% - 20px);
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,156 @@
<template>
<div class="policyForm">
<div class="title">
<div>基础信息</div>
<img src="@/assets/images/titleLine.png" alt="" />
</div>
<a-form
:model="formState"
layout="inline"
label-align="right"
:label-col="{ style: { width: '100px' } }"
>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="名称" :label-col="{ span: 3 }" :wrapper-col="{ span: 18}">
<a-input v-model:value="formState.name" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="类型" :label-col="{ span: 3 }" :wrapper-col="{ span: 18 }">
<a-select v-model:value="formState.region" placeholder="please select your zone">
<a-select-option value="shanghai">Zone one</a-select-option>
<a-select-option value="beijing">Zone two</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="低谷电价" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="平段电价" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="高峰电价" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="尖峰电价" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
</a-col>
</a-row>
<!-- <a-row :gutter="24">
<a-col :span="24">
<a-form-item label="时段表">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">Sponsor</a-radio>
<a-radio value="2">Venue</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="24">
<a-form-item label="充放策略">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">一充一放</a-radio>
<a-radio value="2">两充两放</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="24">
<a-form-item label="充放策略"> 休息休息休息休息 </a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="策略描述">
<a-textarea v-model:value="formState.desc" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否启用">
<a-switch v-model:checked="formState.delivery" />
</a-form-item>
</a-col>
</a-row> -->
</a-form>
</div>
</template>
<script>
import { styleProviderProps } from 'ant-design-vue/es/_util/cssinjs/StyleContext'
export default {
name: '',
components: {},
props: {},
data() {
return {
formState: {
name: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.policyForm {
color: #fff;
.title {
display: flex;
font-weight: 700;
flex-direction: column;
img {
width: 232px;
height: 6px;
margin-top: 10px;
}
}
.ant-form {
}
}
.ant-form-item {
margin-inline-end: 0 !important;
margin-top: 15px;
}
.ant-form {
.ant-form-item-label > label {
color: #fff;
}
:deep(.ant-row) {
width: 100%;
}
.ant-input,
.ant-select,
.ant-input-affix-wrapper {
// max-width: 240px;
// min-width: 120px;
}
textarea{
.ant-input {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div class="policyForm">
<div class="title">
<div>基础信息</div>
<img src="@/assets/images/titleLine.png" alt="" />
</div>
<a-form
:model="formState"
layout="inline"
label-align="left"
:label-col="{ style: { width: '85px' } }"
>
<a-form-item label="名称" class="col2">
<a-input v-model:value="formState.name" />
</a-form-item>
<a-form-item label="类型" class="col2">
<a-select v-model:value="formState.type" placeholder="">
<a-select-option v-for="item in policyTypes" :value="item.value">{{
item.label
}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="低谷电价" class="col4">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
<a-form-item label="平段电价" class="col4">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
<a-form-item label="高峰电价" class="col4">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
<a-form-item label="尖峰电价" class="col4">
<a-input v-model:value="formState.name" suffix="元/kWh" />
</a-form-item>
<a-form-item label="时段表" class="col1">
<a-table :columns="columns" :data-source="data" size="small">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<span>
<a @click="edit(record.key)">操作</a>
</span>
</template>
</template>
</a-table>
</a-form-item>
<a-form-item label="充放策略" class="col1">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">一充一放</a-radio>
<a-radio value="2">两充两放</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="" class="col1">
<div class="charge">
<div class="box">
<span>第一次充放过程</span>
<a-form
:model="formState"
layout="inline"
label-align="left"
:label-col="{ style: { width: '85px' } }"
>
<a-form-item label="充电时间" class="col2">
<a-time-range-picker v-model:value="formState.name" format="HH:mm" />
</a-form-item>
<a-form-item label="放电时间" class="col2">
<a-select v-model:value="formState.type" placeholder="">
<a-select-option v-for="item in policyTypes" :value="item.value">{{
item.label
}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="充电功率" class="col2">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">自动</a-radio>
<a-radio value="2">自定义</a-radio>
<a-input style="width: 60px"></a-input>
</a-radio-group>
</a-form-item>
<a-form-item label="放电功率" class="col2">
<a-radio-group v-model:value="formState.resource">
<a-radio value="1">自动</a-radio>
<a-radio value="2">自定义</a-radio>
<a-input style="width: 60px"></a-input>
</a-radio-group>
</a-form-item>
</a-form>
</div>
<div class="box">222</div>
</div>
</a-form-item>
<a-form-item label="策略描述" class="col2">
<a-textarea v-model:value="formState.desc" />
</a-form-item>
<a-form-item label="是否启用" class="col2">
<a-switch v-model:checked="formState.delivery" />
</a-form-item>
</a-form>
</div>
</template>
<script>
import { policyTypes } from '@/utils/config'
export default {
name: '',
components: {},
props: {},
data() {
return {
policyTypes,
formState: {
name: '',
delivery: false,
type: [],
resource: '',
desc: ''
},
columns: [
{
title: '月份',
dataIndex: 'month',
key: 'month'
},
{
title: '开始时间',
dataIndex: 'startTime',
key: 'startTime'
},
{
title: '时段类型',
dataIndex: 'type',
key: 'type'
},
{
title: '操作',
dataIndex: 'action',
key: 'action'
}
]
}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.policyForm {
color: #fff;
.title {
display: flex;
font-weight: 700;
flex-direction: column;
img {
width: 232px;
height: 6px;
margin-top: 10px;
}
}
}
.ant-form {
.charge {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
.box {
border: 1px solid $table-border;
margin-left: 10px;
border-radius: 10px;
color: #fff;
}
}
}
.ant-form-item {
margin-inline-end: 0 !important;
margin-top: 15px;
:deep(.ant-form-item-label) {
margin-left: 10px;
}
}
.ant-form {
.ant-form-item-label {
> label {
color: #fff;
}
}
:deep(.ant-row) {
width: 100%;
}
input,
.ant-picker,
.ant-select,
.ant-input-affix-wrapper {
width: 150px;
}
textarea {
.ant-input {
width: 100%;
}
}
.col2 {
width: 50%;
}
.col4 {
width: 25%;
}
.col1 {
width: 100%;
:deep(textarea.ant-input) {
width: 100% !important;
}
}
}
:deep(.ant-form-item-row){
// border: 1px solid red;
}
</style>

View File

@@ -9,8 +9,14 @@ import '@/style/index.scss'
// import '@/assets/iconfont/iconfont.css' // import '@/assets/iconfont/iconfont.css'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import VueTianditu from 'vue-tianditu' import VueTianditu from 'vue-tianditu'
import SearchBox from '@/components/SearchBox.vue'
import ComTable from '@/components/ComTable.vue'
const app = createApp(App) const app = createApp(App)
app.component('SearchBox', SearchBox)
app.component('ComTable', ComTable)
app.config.globalProperties.$echarts = echarts // 挂载到全局属性 app.config.globalProperties.$echarts = echarts // 挂载到全局属性
app.use(store).use(router).use(Antd).use(VueTianditu).mount('#app') app.use(store).use(router).use(Antd).use(VueTianditu).mount('#app')

View File

@@ -1,16 +1,26 @@
import request from "@/request/index.js"; import request from '@/request/index.js'
export function postReq(data, url) { import qs from 'qs'
export function postReq(url, data) {
return request({ return request({
method: "post", method: 'post',
url, url,
data, data: {
}); ...data,
token: localStorage.getItem('token')
}
})
} }
export function getReq(data, url) { export function getReq(url, data) {
// const query = qs.stringify(data, { indices: false }) const query = qs.stringify(
{
...data,
token: localStorage.getItem('token')
},
{ indices: false }
)
return request({ return request({
method: "get", method: 'get',
url: url + "?" + data, url: url + '?' + query
}); })
} }

View File

@@ -1,42 +1,42 @@
import axios from "axios"; import axios from 'axios'
// import openNotification from "../utils/notification"; // import openNotification from "../utils/notification";
// let { config } = window; // let { config } = window;
// let { baseUrl } = config; // let { baseUrl } = config;
const service = axios.create({ const service = axios.create({
// baseURL: baseUrl, // baseURL: baseUrl,
baseURL: "", baseURL: '/api',
timeout: 120000, timeout: 120000
}); })
service.interceptors.request.use((config) => { service.interceptors.request.use((config) => {
const webConfig = config; const webConfig = config
// if (!["/user/login", "/config/getConfig"].includes(config.url)) { // if (!['/login'].includes(config.url)) {
// if (localStorage.getItem("token")) { // if (localStorage.getItem('token')) {
// webConfig.headers = { // webConfig.headers = {
// Authorization: localStorage.getItem("token"), // token: localStorage.getItem('token')
// };
// } // }
// } // }
// }
return webConfig; return webConfig
}); })
service.interceptors.response.use( service.interceptors.response.use(
(response) => { (response) => {
// 排除以下接口的错误提示 // 排除以下接口的错误提示
const { url } = response.config; const { url } = response.config
const urls = ["/light/", "/serve/delete", "/user/checkRandom"]; const urls = ['/light/', '/serve/delete', '/user/checkRandom']
const urlFlag = urls.map((item) => { const urlFlag = urls.map((item) => {
return url.includes(item); return url.includes(item)
}); })
const res = response.data; const res = response.data
if (res.code !== 200) { if (res.code !== 200) {
if (res.code == 401 || res.tip == "校验token过期") { if (res.code == 401 || res.tip == '校验token过期') {
setTimeout(() => { setTimeout(() => {
window.$wujie?.props.jump({ path: "/login" }); window.$wujie?.props.jump({ path: '/login' })
}, 1000); }, 1000)
} else if (urlFlag.every((item) => item === false)) { } else if (urlFlag.every((item) => item === false)) {
// openNotification({ // openNotification({
// status: "error", // status: "error",
@@ -44,18 +44,18 @@ service.interceptors.response.use(
// }); // });
} }
} }
return res; return res
}, },
(error) => { (error) => {
// console.log(error, 'error 此处添加监控超时处理') // console.log(error, 'error 此处添加监控超时处理')
if ( if (
error.name === "AxiosError" && error.name === 'AxiosError' &&
error.message === "timeout of 120000ms exceeded" && error.message === 'timeout of 120000ms exceeded' &&
error.code === "ECONNABORTED" error.code === 'ECONNABORTED'
) { ) {
return error; return error
} }
} }
); )
export default service; export default service

View File

@@ -1,19 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
export const routes = [ export const routes = [
{ // {
path: '/', // path: '/',
redirect: '/main/Home' // redirect: '/main/Home'
}, // },
{ {
path: '/login', path: '/login',
name: 'login', name: 'login',
component: () => import(/* webpackChunkName: "login" */ '@/views/LoginView.vue') component: () => import(/* webpackChunkName: "login" */ '@/views/LoginView.vue')
}, },
{ {
path: '/main', path: '/',
name: 'main', name: '/',
redirect: '/main/Home', redirect: '/Home',
component: () => import(/* webpackChunkName: "main" */ '@/views/MainView.vue'), component: () => import(/* webpackChunkName: "main" */ '@/views/MainView.vue'),
children: [ children: [
{ {
@@ -26,6 +26,12 @@ export const routes = [
title: '运行监控', title: '运行监控',
component: () => import(/* webpackChunkName: "monitor" */ '@/views/monitor.vue') component: () => import(/* webpackChunkName: "monitor" */ '@/views/monitor.vue')
}, },
{
path: 'predict',
name: 'predict',
title: '预测管理',
component: () => import(/* webpackChunkName: "predict" */ '@/views/predict.vue')
},
{ {
path: 'statisticalAnalysis', path: 'statisticalAnalysis',
name: 'statisticalAnalysis', name: 'statisticalAnalysis',
@@ -35,7 +41,7 @@ export const routes = [
{ {
path: 'system', path: 'system',
name: 'system', name: 'system',
redirect: '/system/policy', redirect: '/system/user',
component: () => import(/* webpackChunkName: "system" */ '@/views/system/index.vue'), component: () => import(/* webpackChunkName: "system" */ '@/views/system/index.vue'),
children: [ children: [
{ {

View File

@@ -1,49 +1,134 @@
$border-color: #12fbff; $border-color: #12fbff;
$btn-confirm: #1C918A; $btn-confirm: #1c918a;
$btn-del:#D43030; $btn-del: #d43030;
//级联器样式 $btn-edit: #ff8d1a;
$bg1-color: #052f4d;
$bg2-color: #2169c31f;
$bg3-color: #00d2ff1f;
$text-color: #a6b8dd;
$green: #27a188;
$tab-border: #1489c0;
$table-border: #1c797a;
$table-bg: #072e4a;
$page-border: #cad2dd;
//级联器样式,下拉选择器样式输入框等。。。
.ant-select,
.ant-cascader { .ant-cascader {
.ant-select-selector { .ant-select-selector {
background: none !important; background: none !important;
border: 1px solid $border-color !important; border: 1px solid $border-color !important;
color: #fff;
} }
.ant-select-arrow { .ant-select-arrow {
color: $border-color; color: $border-color;
} }
.ant-select-selection-placeholder{ .ant-select-selection-placeholder {
color: #ffffff3b; color: #ffffff3b;
} }
} }
.ant-input,
.ant-input-affix-wrapper,
.ant-picker
{
background: none !important;
border: 1px solid $border-color !important;
color: #fff;
}
:deep(.ant-picker){
.ant-picker-input >input,.ant-picker-separator{
color: #fff !important;
}
.ant-picker-input::placeholder{
color: #ffffff3b !important;
}
}
.ant-input-affix-wrapper {
.ant-input {
border: none !important;
}
}
.ant-radio-wrapper {
color: #fff;
}
//表单
.ant-form {
.ant-form-item-label > label {
color: #fff;
}
}
//按钮样式 //按钮样式
// .ant-btn{ // .ant-btn{
// padding: 4px 8px; // padding: 4px 8px;
// } // }
.ant-btn-primary{ .ant-btn-primary {
background: $btn-confirm;
&:hover {
background: $btn-confirm; background: $btn-confirm;
&:hover{ opacity: 0.8;
background: $btn-confirm; }
opacity: 0.8; &:active {
} background: #0f6f6a;
&:active{ }
background: #0f6f6a;
}
} }
.btn-del{ .btn-del {
background: $btn-del; background: $btn-del;
&:hover{ &:hover {
background: $btn-del; background: $btn-del;
opacity: 0.8; opacity: 0.8;
} }
&:active{ &:active {
background: $btn-del; background: $btn-del;
}
} }
.btn-edit {
background: $btn-edit;
&:hover {
background: $btn-edit;
opacity: 0.8;
}
&:active {
background: $btn-edit;
}
} }
//modal样式 //modal样式
.ant-modal .ant-modal-content{ .ant-modal .ant-modal-content {
background-image: url('@/assets/images/modalBg.png'); background-image: url('@/assets/images/modalBg.png');
background-size: 100% 100%; background-size: 100% 100%;
background-color: #ffffff00 !important; background-color: #ffffff00 !important;
border-radius: 0; border-radius: 0;
} }
.ant-modal .ant-modal-footer {
text-align: center;
}
//表单中的表格样式
.ant-form{
.ant-table-thead {
background: rgba(30, 85, 95, 1) !important;
}
:deep(.ant-table-thead > tr > th) {
border-inline: 1px solid transparent !important;
background: transparent;
color: #fff !important;
border-bottom: none !important; /* 可选:去除底部边框 */
}
:deep(.ant-table-tbody){
color: #fff;
background: $table-bg ;
border: 1px solid $table-border !important;
border-radius: 0px 0px 20px 20px;
}
:deep(.ant-table-wrapper .ant-table-tbody>tr.ant-table-placeholder:hover>td){
background: transparent !important;
}
:deep(.ant-empty-description){
color: #fff !important;
}
}

View File

@@ -1,39 +1,14 @@
const copyRight = export const policyTypes = [
'Copyright © 2016-2024 Jiangsu YuHong.All Rights Reserved.江苏禹弘科技有限公司 版权所有' {
const companyName1 = '江苏禹弘科技有限公司' value: 1,
const copyRight1 = 'Copyright © 2016 - 2024' label: '削峰套利'
const themeColor = { },
'--theme-bg-default': { light: '#fff', dark: '#111111' }, {
'--theme-bg': { light: '#eceff4', dark: '#2c2c2c' }, value: 2,
'--theme-bg1': { light: '#065758', dark: '#065758' }, label: '需求响应'
'--theme-bg2': { light: '#0657584d', dark: '#CDDDDE' }, },
'--theme-bg3': { light: '#00968826', dark: '#00968826' }, {
'--theme-bg4': { light: 'rgba(6, 87, 88, 0.75)', dark: 'rgba(6, 87, 88, 0.75)' }, value: 3,
label: '自发自用'
// 字体颜色 }
'--theme-text-default': { light: '#000000', dark: '#ffffff' }, ]
'--theme-text': { light: '#065758', dark: '#065758' },
'--theme-text1': { light: 'rgba(0,0,0,0.5)', dark: '#040909FF' },
'--theme-text2': { light: '#ffffff', dark: '#ffffff' },
'--theme-text3': { light: 'rgba(0,0,0,0.5)', dark: 'rgba(255,255,255,0.5)' },
'--theme-text4': { light: 'rgba(0,0,0,0.75)', dark: 'rgba(255,255,255,0.75)' },
'--theme-text5': { light: 'rgba(0,0,0,0.65)', dark: 'rgba(255,255,255,0.65)' },
'--theme-text6': { light: 'rgba(0,0,0,0.3)', dark: 'rgba(255,255,255,0.3)' }, // placeholder颜色
// 按钮颜色
'--theme-btn1': { light: '#FF921B', dark: '#FF921B' },
'--theme-btn2': { light: '#ca4d2a', dark: '#ca4d2a' },
'--theme-btn3': { light: '#065758', dark: '#065758' },
// 阴影颜色
'--shadow-color1': { light: '#0657584d', dark: '#065758a6' },
'--shadow-color': { light: 'rgba(0, 0, 0, 0.25)', dark: 'rgba(255, 255, 255, 0.25)' },
// 表格颜色
'--table-header-bg': { light: '#5d9292', dark: '#065253' }, // 65%
'--table-tag': { light: '#D6DEEB', dark: '#D6DEEB' },
'--theme-opert-bg': { light: '#FFFFFF', dark: '#2c2c2c' },
'--table-select': { light: '#c4d4d9', dark: '#384846' }
}
export { copyRight, copyRight1, themeColor, companyName1 }

View File

@@ -10,9 +10,9 @@
<div class="main-title">能源站监控与运行管理系统</div> <div class="main-title">能源站监控与运行管理系统</div>
<div class="login-content"> <div class="login-content">
<div class="title" style="">账号登录</div> <div class="title" style="">账号登录</div>
<a-form ref="ruleForm" :model="form" :rules="rules"> <a-form ref="ruleForm" :model="form" :rules="rules" >
<a-form-item label="" name="user"> <a-form-item label="" name="account">
<a-input v-model:value="form.user" placeholder="请输入账号" autocomplete> <a-input v-model:value="form.account" placeholder="请输入账号" autocomplete>
<template #prefix> <template #prefix>
<user-outlined /> <user-outlined />
</template> </template>
@@ -39,7 +39,7 @@
</template> </template>
<script> <script>
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue' import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { postReq,getReq } from '@/request/api.js'
export default { export default {
name: 'LoginView', name: 'LoginView',
components: { components: {
@@ -49,11 +49,11 @@ export default {
data() { data() {
return { return {
form: { form: {
user: 'admin', account: 'admin',
passwd: '123456' passwd: '123456'
}, },
rules: { rules: {
user: [ account: [
{ {
required: true, required: true,
message: '请输入账号' message: '请输入账号'
@@ -71,27 +71,30 @@ export default {
}, },
methods: { methods: {
async login() { async login() {
this.$refs.ruleForm.validate(async (valid) => { try {
if (valid) { const values = await this.$refs.ruleForm.validateFields()
this.loading = true const res = await getReq('/login',this.form )
try { this.loading = false
const res = await this.$http.post('/login', this.form) console.log(res);
this.loading = false
if (res.code === 200) { // if (res.code === 200) {
this.$message.success('登录成功') this.$message.success('登录成功')
localStorage.setItem('token', res.token) localStorage.setItem('token', res.token)
this.$router.push('/main') this.$router.push('/')
} else { // } else {
this.$message.error(res.message || '登录失败') // this.$message.error(res.message || '登录失败')
} // }
} catch (error) { } catch (error) {
this.loading = false console.log(error);
this.$message.error('请求失败,请稍后重试')
} this.loading = false
} else { this.$message.error('请求失败,请稍后重试')
// console.log("表单验证失败");
}
}) }
} }
} }
} }

View File

@@ -47,15 +47,20 @@ export default {
menuList: [ menuList: [
{ {
name: '系统总览', name: '系统总览',
icon: 'icon-xitongguanli' path:'/home'
}, },
{ {
name: '运行监控', name: '运行监控',
path: '/main/monitor' path: '/monitor'
},
{
name: '预测管理',
path: '/predict'
}, },
{ {
name: '统计分析', name: '统计分析',
path: '/main/statisticalAnalysis' path: '/statisticalAnalysis'
}, },
{ {
name: '系统管理', name: '系统管理',

View File

@@ -4,36 +4,43 @@
<div class="left"> <div class="left">
<div class="search-item"> <div class="search-item">
<span>场站切换</span> <span>场站切换</span>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" /> <a-select v-model:value="selectStation" style="width: 220px">
<a-select-option v-for="station in stations" :value="station['station_id']"
>{{ station.name }}
</a-select-option>
</a-select>
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<div class="search-item"> <div class="search-item">
<span>运行模式</span> <span>运行模式</span>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" /> <a-select v-model:value="value" style="width: 220px">
<a-select-option value="lucy">Lucy</a-select-option>
</a-select>
</div> </div>
<div class="search-item"> <div class="search-item">
<span>策略名称</span> <a-button type="primary">下发</a-button>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" />
</div>
<div class="search-item">
<a-button type="primary">调控</a-button>
</div> </div>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div class="stations"> <div class="stations">
<div class="station-item" v-for="station in stations" :key="station.name" @click="()=>currentKey=station.name" :class="currentKey==station.name?'active':''"> <div
<span class="name">{{ station.name }}</span> class="station-item"
<span class="des">总功率{{ station.power }} W</span> v-for="system in systems"
<span class="des">数量{{ station.num }}</span> :key="system.name"
@click="chooseStation(system)"
:class="systemType == system.systemType ? 'active' : ''"
>
<span class="name">{{ system.name }}</span>
<span class="des">边缘网关{{ system.power }} W</span>
<span class="des">总有功功率(台区){{ system.num }}</span>
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<device v-if="stationType" /> <videos v-if="systemType == 4" />
<videos v-else /> <device v-else :station-id="selectStation" :system-type="systemType"/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@@ -41,6 +48,7 @@
<script> <script>
import device from '@/components/monitor/device.vue' import device from '@/components/monitor/device.vue'
import videos from '@/components/monitor/videos.vue' import videos from '@/components/monitor/videos.vue'
import { postReq, getReq } from '@/request/api'
export default { export default {
name: 'MonitorView', name: 'MonitorView',
@@ -50,63 +58,64 @@ export default {
}, },
data() { data() {
return { return {
currentKey:'储能系统1', // currentKey: '储能系统',
stationType: 1, systemType: 1,
value: [], value: [],
options: [ stations: [],
selectStation:'',
systems: [
{ {
value: 'zhejiang', name: '储能系统',
label: 'Zhejiang', power: 60,
children: [ num: 62,
{ systemType: 1
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake'
}
]
}
]
}, },
{ {
value: 'jiangsu', name: '充电系统',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen'
}
]
}
]
}
],
stations: [
{
name: '储能系统1',
power: 60, power: 60,
num: 62 num: 62,
systemType: 2
}, },
{ {
name: '储能系统2', name: '光伏系统',
power: 60, power: 60,
num: 62 num: 62,
systemType: 3
},
{
name: '安防系统',
power: 60,
num: 62,
systemType: 4
} }
// { // {
// name: "储能系统3",
// power: 60,
// num: 62
// },
// {
// name: "储能系统4", // name: "储能系统4",
// } // }
] ]
} }
},
mounted() {
this.getStations()
},
methods: {
//查询场站列表
async getStations() {
try {
const res = await getReq('/queryStationList', { page: 0, 'page_size': 10000 })
console.log(res)
this.stations = res.data
this.selectStation=this.stations[0]['station_id']
} catch (error) {
this.stations = []
this.selectStation=''
this.$message.error(error.message)
}
},
chooseStation(system) {
this.systemType = system.systemType
}
} }
} }
</script> </script>
@@ -157,12 +166,12 @@ export default {
width: calc(100% - 30px); width: calc(100% - 30px);
margin: 0 15px 15px 15px; margin: 0 15px 15px 15px;
border-radius: 12px; border-radius: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: #fff; color: #fff;
padding: 10px 15px; padding: 10px 15px;
cursor: pointer; cursor: pointer;
.name { .name {
font-size: 20px; font-size: 20px;
@@ -175,13 +184,13 @@ export default {
line-height: 40px; line-height: 40px;
} }
} }
.active{ .active {
background: $bg3-color; background: $bg3-color;
} }
} }
.container { .container {
width: 87%; width: 87%;
display: flex; display: flex;
} }
} }
} }

293
web/src/views/predict.vue Normal file
View File

@@ -0,0 +1,293 @@
<template>
<div class="predict">
<div class="top">
<predictEcharts :chart-options="chartOptions[0]" :chart-data="chartData"/>
</div>
<div class="bottom">
<div class="item">
<predictEcharts :chart-options="chartOptions[1]" :chart-data="chartData"/>
</div>
<div class="item">
<predictEcharts :chart-options="chartOptions[2]" :chart-data="chartData"/>
</div>
</div>
</div>
</template>
<script>
import predictEcharts from '@/components/predict/predictEcharts.vue';
export default {
name: '',
components: {predictEcharts},
props: {},
data() {
return {
chartOptions:[ {
title: '储能充放电预测',
type: 'line',
smooth:false,
dataKey: 'chargeDischarge',
infoKeys: [
{
key: 'key1',
label: '电压',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#00FDF9' // 充电电量线条颜色
},
lineStyle: {
color: '#00FDF9' // 充电电量线条颜色
}
}
},
{
key: 'key2',
label: '电流',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#3E7EEF' // 充电电量线条颜色
},
lineStyle: {
color: '#3E7EEF' // 充电电量线条颜色
}
}
}
]
},
{
title: '充电负荷预测',
type: 'line',
smooth:false,
dataKey: 'chargeDischarge',
infoKeys: [
{
key: 'key1',
label: '电压',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#00FDF9' // 充电电量线条颜色
},
lineStyle: {
color: '#00FDF9' // 充电电量线条颜色
}
}
},
{
key: 'key2',
label: '电流',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#3E7EEF' // 充电电量线条颜色
},
lineStyle: {
color: '#3E7EEF' // 充电电量线条颜色
}
}
}
]
},
{
title: '光伏发电预测',
type: 'line',
smooth:false,
dataKey: 'chargeDischarge',
infoKeys: [
{
key: 'key1',
label: '电压',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#00FDF9' // 充电电量线条颜色
},
lineStyle: {
color: '#00FDF9' // 充电电量线条颜色
}
}
},
{
key: 'key2',
label: '电流',
seriesOptions:{
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#3E7EEF' // 充电电量线条颜色
},
lineStyle: {
color: '#3E7EEF' // 充电电量线条颜色
}
}
}
]
},
],
chartData: [
{
date: '2025-08-30',
key1: 10,
key2: 10,
key3: 15,
key4: 5
},
{
date: '2025-08-29',
key1: 8,
key2: 5,
key3: 5,
key4: 7
},
{
date: '2025-08-28',
key1: 10,
key2: 10,
key3: 20,
key4: 4
},
{
date: '2025-08-27',
key1: 10,
key2: 10,
key3: 15,
key4: 5
},
{
date: '2025-08-26',
key1: 10,
key2: 5,
key3: 15,
key4: 5
},
{
date: '2025-08-25',
key1: 10,
key2: 5,
key3: 15,
key4: 5
},
{
date: '2025-08-24',
key1: 10,
key2: 6,
key3: 15,
key4: 5
},
{
date: '2025-08-23',
key1: 10,
key2: 7,
key3: 15,
key4: 5
},
{
date: '2025-08-22',
key1: 10,
key2: 0,
key3: 15,
key4: 5
},
{
date: '2025-08-21',
key1: 10,
key2: 0,
key3: 15,
key4: 5
},
{
date: '2025-08-20',
key1: 10,
key2: 0,
key3: 15,
key4: 5
},
{
date: '2025-08-19',
key1: 10,
key2: 0,
key3: 15,
key4: 5
},
{
date: '2025-08-18',
key1: 10,
key2: 0,
key3: 15,
key4: 5
},
{
date: '2025-08-17',
key1: 10,
key2: 0,
key3: 15,
key4: 5
}
],
}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.predict {
width: 100%;
height: 100%;
background: $bg1-color;
border-radius: 15px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
.top,
.bottom {
flex: 1;
width: 100%;
height: calc(50% - 10px);
}
.bottom {
display: flex;
}
.top{
background-color: rgba(33, 105, 195, 0.12);
padding: 20px 5px;
border-radius: 15px;
}
.item{
background-color: rgba(33, 105, 195, 0.12);
padding: 20px 5px;
flex: 1;
border-radius: 15px;
&:nth-child(2){
margin-left: 20px;
}
}
}
</style>

View File

@@ -1,46 +1,209 @@
<template> <template>
<div class="policy"> <div class="policy">
<searchBox :btn-option-list="btnOptionList" @onSearch="onSearch" @operateForm="operateForm"/> <searchBox :btn-option-list="btnOptionList" @onSearch="onSearch" @operateForm="operateForm" />
<div class="content-table">
<ComTable
:columns="columns"
:table-data="tableData"
@handlePagesizeChange="handlePagesizeChange"
ref="comTable"
:table-option="tableOption"
:page-option="pageOption"
:table-h="tableH"
>
<template #type="record">
<div>{{record.type}}</div>
</template>
<template #action >
<a-button type="primary" size="small" @click="operateForm('edit')" style="margin-left: 10px">查看</a-button>
<a-button type="primary" size="small" @click="operateForm('edit')" class="btn-edit" style="margin-left: 10px">修改</a-button>
<a-button type="primary" size="small" @click="operateForm('edit')" class="btn-del" style="margin-left: 10px">删除</a-button>
</template>
</ComTable>
</div>
</div>
<div>
<a-modal v-model:open="formModal" @ok="handleOk" width="70%">
<policyForm/>
</a-modal>
</div> </div>
</template> </template>
<script> <script>
import searchBox from '@/components/SearchBox.vue' import policyForm from '@/components/system/policyForm.vue'
import {getReq} from '@/request/api'
export default { export default {
name: '', name: '',
components:{searchBox components: { policyForm },
}, props: {},
props: { data() {
},
data() {
return { return {
btnOptionList:[ formModal:true,
{label:'新增',icon:'icon-tianjia',type:'add'}, btnOptionList: [
{label:'删除',icon:'icon-tianjia',type:'del'} { label: '新增', icon: 'icon-tianjia', type: 'add' },
{ label: '删除', icon: 'icon-tianjia', type: 'del' }
] ],
columns: [
{
title: '策略ID',
dataIndex: 'policyId',
key: 'policyId',
width: 120,
ellipsis: true
},
{
title: '策略名称',
dataIndex: 'name',
key: 'name',
width: 120,
ellipsis: true
},
{
title: '策略类型',
dataIndex: 'type',//策略类型1削峰套利2需求响应3自发自用
key: 'type',
width: 120,
ellipsis: true,
scopedSlots: { customRender: 'type' }
},
{
title: '策略描述',
dataIndex: 'describe',
key: 'describe',
width: 120,
ellipsis: true
},
{
title: '策略参数',
dataIndex: 'value',
key: 'value',
width: 120,
ellipsis: true
},
{
title: '是否启用',
dataIndex: 'isOpen',
key: 'isOpen',
width: 120,
ellipsis: true,
scopedSlots: { customRender: 'isOpen' }
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 150,
ellipsis: true,
scopedSlots: { customRender: 'action' }
}
],
tableData:[{
policyId: 'P001',
name: '峰谷套利策略',
type: 1, // 削峰套利
describe: '利用峰谷电价差进行储能充放电优化',
value: '峰时段: 08:00-22:00, 谷时段: 22:00-08:00',
isOpen: true,
action: 'edit|delete'
},
{
policyId: 'P002',
name: '需求响应策略',
type: 2, // 需求响应
describe: '参与电网调峰的需求侧响应方案',
value: '响应阈值: 80%, 持续时间: 2小时',
isOpen: false,
action: 'edit|delete'
},
{
policyId: 'P003',
name: '光伏自发自用',
type: 3, // 自发自用
describe: '优先使用光伏发电,余电上网',
value: '自用比例: 90%, 上网比例: 10%',
isOpen: true,
action: 'edit|delete'
},
{
policyId: 'P004',
name: '工业削峰策略',
type: 1, // 削峰套利
describe: '通过储能系统平抑工业用电高峰',
value: '削峰容量: 500kWh, 持续时间: 4小时',
isOpen: true,
action: 'edit|delete'
},
{
policyId: 'P005',
name: '商业综合体响应',
type: 2, // 需求响应
describe: '商业建筑参与电网需求响应',
value: '可中断负荷: 200kW, 响应次数: 3次/月',
isOpen: false,
action: 'edit|delete'
}],
tableOption:{},
pageOption:{
page:1,
pageSize:10,
},
tableH:''
} }
}, },
mounted() { mounted() {
this.getTableList()
}, },
methods:{ methods: {
onSearch(data){ async getTableList(){
try {
const res = await getReq('/queryPolicyList', {
page: this.pageOption.page,
'page_size': this.pageOption.pageSize
})
} catch (error) {
console.log(error);
}
},
handleOk(){
this.formModal=false
},
onSearch(data) {
console.log(data) console.log(data)
}, },
operateForm(type){ operateForm(type) {
console.log(type ) console.log(type)
switch (type){
case 'add':
this.formModal= true
break;
default:
break;
}
}, },
handlePagesizeChange(data) {
console.log(data)
}
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.policy{ .policy {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 15px; padding: 0 15px;
.content-table {
width: 100%;
height: calc(100% - 92px );
} }
</style> }
</style>

View File

@@ -32,6 +32,15 @@ module.exports = defineConfig({
return true return true
} }
} }
},
proxy: {
'/api': {
target: 'http://192.168.0.187:19801', // 目标服务器地址
changeOrigin: true, // 修改请求头中的host
pathRewrite: {
'^/api': '' // 重写路径,去掉/api前缀
}
}
} }
}, },
css: { css: {
@@ -44,8 +53,7 @@ module.exports = defineConfig({
} }
}, },
extract: { extract: {
filename: `css/.[contenthash:8].css`, ignoreOrder: true // 忽略 CSS 顺序警告
chunkFilename: `css/.[contenthash:8].chunk.css`
} }
}, },
// webpack相关配置 // webpack相关配置