合并代码

This commit is contained in:
ym1026
2025-09-01 17:01:35 +08:00
68 changed files with 3110 additions and 618 deletions

View File

@@ -15,6 +15,7 @@ module.exports = {
},
plugins: ['react', '@typescript-eslint', 'prettier'], // 添加 prettier 插件
rules: {
'vue/require-explicit-emits': 'off', // 关闭 emits 声明检查
'vue/v-on-event-hyphenation': 'off',
'prettier/prettier': 'off',
'react/react-in-jsx-scope': 'off', // React 17+ 可关闭 JSX 运行时检查
@@ -24,9 +25,8 @@ module.exports = {
eqeqeq: 0, // 警告使用全等
quotes: [0, 'single'], // 单引号
singleQuote: 0,
'no-console': ['off'], // 允许所有 console 语句
// 'no-console': 2, // 不禁用console
'no-debugger': 2, // 警告debugger
'no-console': 0, // 不禁用console
'no-debugger': 0, // 警告debugger
'no-var': 2, // 对var禁止
'no-eval': 0,
semi: 0, // 强制使用分号

View File

@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve && webpack-dev-server",
"serve": "vue-cli-service serve ",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"show-webpack-version": "webpack --version"

View File

@@ -6,8 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_5010233_m6gk6vpoxg.css" />
<link rel="stylesheet" href="http://at.alicdn.com/t/c/font_5010233_d9pawp3ats.css" />
</head>
<body>
<noscript>

View File

@@ -1,5 +1,13 @@
<template>
<a-config-provider
:theme="{
token: {
colorPrimary: '#143d7d'
}
}"
>
<router-view />
</a-config-provider>
</template>
<style lang="scss">
@@ -9,7 +17,7 @@
left: 0;
bottom: 0;
right: 0;
// min-width: 1440px;
// min-height: 900px;
min-width: 1440px;
min-height: 900px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,447 @@
<template>
<div class="comtable" >
<div class="table" ref="comtable">
<a-table
bordered
:loading="loading"
:columns="columns"
:scroll="scroll"
:data-source="data.realTableData"
:pagination="false"
:row-class-name="rowClassName"
row-key="id"
:expand-icon="expandIcon"
:row-selection="
data.newTableOpt.select
? {
selectedRowKeys: data.selectedRowKeys,
onChange: onSelectChange
}
: false
"
:expanded-row-keys="data.newTableOpt.expand?data.expandedKeys:null"
size="middle"
:indent-size="30"
@resizeColumn="handleResizeColumn"
>
<template #bodyCell="{ column, record }">
<template v-if="column.scopedSlots">
<slot
v-bind="record"
:name="column.scopedSlots ? column.scopedSlots.customRender : ''"
></slot>
</template>
</template>
<template v-if="data.newTableOpt.expand" #expandedRowRender="{ record }">
<a-table
size="small"
:columns="innerColumns"
:data-source="record.currentLimitList"
:pagination="false"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<span>
{{ getType(record.type) }}
</span>
</template>
</template>
</a-table>
</template>
</a-table>
</div>
<div class="pagination" v-if="data.newTableOpt.page">
<a-pagination
v-model:current="data.newPageOption.current"
:total="data.newPageOption.total"
:page-size="data.newPageOption.pageSize"
@change="onChange"
show-size-changer
show-quick-jumper
:show-total="(total) => `${total}`"
:page-size-options="data.pageSizeOptions"
>
</a-pagination>
</div>
</div>
</template>
<script setup>
// <a-select :dropdownMatchSelectWidth="false" size="small" placement="top" />
import {
nextTick,
ref,
toRefs,
reactive,
onMounted,
watch,
defineProps,
defineEmits,
defineExpose,
} from 'vue'
const comtable = ref('')
const props = defineProps({
columns: { type: Array },
tableData: { type: Array },
tableOption: {
type: Object,
default: () => {
return {
loading: false,
page: true,
align: 'center',
expand: false,
select: true,
scroll: { y: 750 }
}
}
},
pageOption: {
type: Object,
default: () => {
return {
current: 1,
pageSize: 10,
total: 1
}
}
},
selectField: {
type: Array,
default: () => {
return []
}
},
rowClick: {
default: () => {}
},
tableH: {
type: Number,
},
})
function handleResizeColumn(w, col) {
col.width = w
}
const mountedScroll = () => {
data.newColumns = [...props.columns]
data.realTableData = [...props.tableData]
// tableScoll()
}
const emit = defineEmits(['handlePagesizeChange'])
const data = reactive({
expandedKeys: [],
newColumns: [],
selectedRowKeys: [],
realColumns: [],
realTableData: [],
selectedRows: [],
defaultTabOpt: {
page: true,
align: 'center',
expand: false,
select: true
},
newPageOption: {},
newTableOpt: {},
pageSizeOptions: ['15', '20', '30', '40', '50', '100'],
mountedScroll
})
const loading = ref(false)
const scroll = ref({})
onMounted(async() => {
data.newColumns = [...props.columns]
data.realTableData = [...props.tableData]
await nextTick()
// console.log(props.tableH, 'props.tableH');
scroll.value = { y: comtable.value.offsetHeight - 56 }
})
watch(
() => props.tableData,
(n, o) => {
if (n) {
data.realTableData = [...n]
}
},
{ deep: true, immediate: true }
)
watch(
() => props.pageOption,
(n, o) => {
data.newPageOption = { ...n }
},
{ deep: true, immediate: true }
)
watch(
() => props.tableOption,
(n, o) => {
data.newTableOpt = { ...data.defaultTabOpt, ...n }
},
{ deep: true, immediate: true }
)
watch(
() => props.tableH,
(n, o) => {
if (n && n !== o) {
const pageH = data.newTableOpt.page ? 42 : 0
scroll.value = { y: n - pageH - 56 }
}
},
// { deep: true, immediate: true }
)
function rowClassName(record, index) {
return 'table-row'
}
function expandIcon(props) {}
function onChange(page, pageSize) {
data.newPageOption.current = page
data.newPageOption.pageSize = pageSize
emit('handlePagesizeChange', data.newPageOption)
}
function onSelectChange(selectedRowKeys, selectedRows) {
data.selectedRowKeys = selectedRowKeys
data.selectedRows = selectedRows
}
defineExpose({ ...toRefs(data), loading, mountedScroll, scroll: data.scroll })
</script>
<style lang="scss" scoped>
@use '../style/color.scss' as *;
.comtable {
border-radius: 8px;
font-size: 14px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.table{
flex:1
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
// margin-bottom: 10px;
min-width: 420px;
height: 32px;
}
::v-deep .ant-table-thead > tr > th {
border-inline: 1px solid var(--theme-bg) !important;
background: var(--table-header-bg);
color: var(--theme-text-default);
border-bottom: none;
// &.ant-table-column-has-sorters{
// &::hover{
// background: var(--table-header-bg) !important;
// }
// }
&:last-child {
border-inline-end: 1.5px solid var(--table-header-bg) !important;
}
&:nth-last-child(2) {
border-inline-end: 1.5px solid var(--table-header-bg) !important;
}
}
:deep(.ant-table-container > .ant-table-content > table) {
border-inline-start: 1px solid var(--theme-bg) !important;
}
:deep(.ant-pagination-item-link) {
color: var(--theme-text-default) !important;
height: 100% !important;
display: block !important;
}
}
:deep(
.ant-table-wrapper
.ant-table.ant-table-bordered
> .ant-table-container
> .ant-table-header
> table
) {
border-top: none !important;
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: var(--table-header-bg) !important;
border: none !important;
}
:deep(.ant-checkbox-indeterminate .ant-checkbox-inner:after) {
background-color: var(--table-header-bg) !important;
}
:deep(.ant-pagination) {
.ant-pagination-prev,
.ant-pagination-next {
background: var(--theme-bg) !important;
color: var(--theme-text-default) !important;
}
.ant-select-selector {
border: none !important;
}
.ant-select-arrow {
color: var(--theme-text-default) !important;
}
span.ant-input-affix-wrapper,
.ant-select,
.ant-picker {
width: 110px !important;
border-radius: 8px !important;
}
.ant-select-selection-item {
color: var(--theme-text-default) !important;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
background-color: var(--theme-bg) !important;
border-radius: 8px !important;
}
.ant-pagination-total-text,
.ant-pagination-options-quick-jumper {
color: var(--theme-text-default) !important;
margin-inline-end: 9px !important;
}
.ant-pagination-options-quick-jumper input {
background-color: var(--theme-bg) !important;
border: none !important;
color: var(--theme-text-default) !important;
}
.ant-pagination-options .ant-pagination-options-size-changer .ant-select-selector {
color: var(--theme-text-default) !important;
background-color: var(--theme-bg) !important;
}
.ant-select-dropdown {
top: -210px !important;
}
.ant-pagination-item {
&:not(.ant-pagination-item-active):hover{
background: var(--theme-bg) !important;
}
a{
color: var(--theme-text-default) !important;
}
}
.ant-pagination-item-ellipsis {
color: var(--theme-text-default) !important;
}
.ant-pagination-item-active {
border-color: transparent !important;
font-size: 16px;
background: var(--theme-bg1) !important;
a {
color: var(--theme-text2) !important;
}
}
}
:deep(
.ant-table.ant-table-bordered > .ant-table-container > .ant-table-header > table > thead > tr > th
) {
&:nth-last-child(2) {
border-inline-end: 1px solid var(--table-header-bg) !important;
}
}
//表格样式
:deep(.ant-table-tbody) {
color: var(--theme-text-default) !important;
> tr {
&:hover {
> .ant-table-cell {
background-color: var(--table-select) !important;
}
}
td {
border: 1px solid var(--theme-bg-default) !important; /* 第一行单元格边框为红色 */
}
}
// td.ant-table-cell.ant-table-cell-row-hover {
// // background-color: var(--table-select) !important;
// }
}
:deep(.ant-table-body) {
background: var(--theme-bg) !important;
.ant-table-cell {
background: var(--theme-bg) !important;
}
.ant-table-row-selected {
td {
background-color: var(--table-select) !important;
}
}
.ant-table-cell-fix-right.ant-table-cell-fix-right-first,
.ant-table-cell-fix-left {
box-shadow: none !important;
padding: 8px !important;
margin: 0 !important;
}
}
:deep(.ant-table-wrapper .ant-table.ant-table-bordered > .ant-table-container) {
border-inline-start: none !important;
:deep(.ant-progress .ant-progress-text) {
color: var(--theme-text-default) !important;
}
}
:deep(.ant-table-wrapper) {
.ant-table-cell-scrollbar,
.ant-table.ant-table-bordered > .ant-table-container {
box-shadow: none !important;
}
}
:deep(.ant-table-wrapper .ant-table) {
background-color: transparent !important;
}
:deep(
.ant-table-wrapper
.ant-table.ant-table-bordered
> .ant-table-container
> .ant-table-body
> table
> tbody
> tr
> .ant-table-cell-fix-right-first::after
) {
border-inline-end: 1px solid var(--theme-bg) !important;
}
:deep(
.ant-table-wrapper
.ant-table.ant-table-bordered
> .ant-table-container
> .ant-table-header
> table
> thead
> tr
> .ant-table-cell-fix-right-first::after
) {
border-inline-end: 1px solid var(--theme-bg) !important;
}
:deep(.ant-table-wrapper .ant-table-thead th.ant-table-column-has-sorters:hover) {
background: var(--table-select) !important;
}
</style>

View File

@@ -0,0 +1,332 @@
<template>
<div class="search">
<div class="top" v-if="searchOptions.length">
<div class="top-left">
<template v-for="item in searchOptions" :key="item.key">
<!-- 输入框 @change="handleChange"-->
<div class="item">
<span class="label"> {{ item.label }}</span>
<div class="select" v-if="item.type == 'select'">
<a-select
:dropdown-match-select-width="false" v-model:value="formData[item.key]"
allow-clear
:max-tag-count='2'
:placeholder="item.label"
:mode="item.mode ? item.mode : 'combobox'"
>
<a-select-option
:value="option.value"
v-for="option in item.options"
:key="option.value"
>
{{ option.label }}
</a-select-option>
</a-select>
</div>
<!-- 日期选择框 -->
<div class="date-picker" v-if="item.type == 'datePick'">
<a-range-picker
:show-time="{ format: 'HH:mm:ss' }"
value-format="YYYY-MM-DD HH:mm:ss"
v-model:value="formData[item.key]"
/>
</div>
<!-- 输入框 -->
<div class="input" v-if="item.type == 'input'">
<a-input
v-model:value="formData[item.key]"
:placeholder="item.placeholder ? item.placeholder : '请输入' + item.label"
:disabled="item.disabled"
>
</a-input>
</div>
<!-- 插槽 -->
<div class="slot" v-if="item.type == 'slot'">
<slot :name="item.slotName" v-bind="item"></slot>
</div>
</div>
</template>
</div>
<!-- <div :class="[searchOptions.length > 4 ? 'top-right-column' : 'top-right']"> -->
<div class="top-right">
<a-button class="ant-btn-search" @click="changeData">
<template #icon>
<i class="iconfont icon-sousu btn-close" />
</template>
查询
</a-button>
<a-button class="ant-btn-reset" @click="clearData">
<template #icon>
<i class="iconfont icon-chongzhi btn-close" />
</template>
重置
</a-button>
</div>
</div>
<div class="bottom">
<div style="display: flex" v-if="btnOptionList.length">
<div v-for="(item, i) in btnOptionList" :key="i" class="button">
<a-button
:class="'btn-' + item.type"
type="primary"
@click="handelClick(item.type)"
:disabled="item.disabled ? item.disabled : false"
>
{{ item.label }}
</a-button>
</div>
</div>
<slot name="searchboxSlot"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'SearchBox',
components: {},
props: {
titleOption: {
type: Object,
default: () => {
return {
title: '',
info: ''
}
}
},
btnOptionList: {
type: Array,
default: () => {
return []
}
},
searchOptions: {
type: Array,
default: () => {
return []
}
},
fieldList: {
type: Array,
default: () => {
return []
}
}
},
data() {
return {
showTableDropMenu: false,
clearFlag: false,
selectField: [],
formData: {},
options: [],
searchList: []
}
},
watch: {
searchOptions: {
handler(n) {
// this.formData = {}
// n.forEach((item) => {
// // this.$set(this.formData, item.key, item.value)
// })
},
immediate: true,
deep: true
}
},
mounted() {
this.selectField = this.fieldList.map((i) => i.value)
},
methods: {
clear(date) {
if (date.length == 0) {
this.$emit('onSearch', this.formData)
}
},
changeData() {
this.clearFlag = false
this.$emit('onSearch', this.formData)
},
clearData() {
this.clearFlag = true
this.formData = {}
this.$emit('onSearch', this.formData)
},
reseatFormData() {
this.formData = {}
},
handelClick(type) {
this.$emit('operateForm', type)
}
// handleChange() {
// this.$emit("onSearch", this.formData);
// },
// pressEnter() {
// this.$emit("onSearch", this.formData);
// },
}
}
</script>
<style lang="scss" scoped>
// 输入框自动填充后的背景改色
input:-internal-autofill-previewed,
input:-internal-autofill-selected {
-webkit-text-fill-color: var(--theme-text-default);
transition: background-color 500s ease-out 0.5s;
}
:deep(.anticon) {
color: var(--theme-text-default) !important;
}
.search {
display: flex;
flex-direction: column;
justify-content: space-between;
.page-title {
display: flex;
align-items: center;
margin-bottom: 15px;
margin-top: 5px;
color: var(--theme-text-default);
.line {
width: 3px;
height: 20px;
background-color: var(--theme-btn3);
border-radius: 1px;
}
.title-text {
font-size: 18px;
margin-left: 10px;
}
.iconfont {
margin-left: 10px;
font-size: 20px;
color: var(--theme-text3);
}
}
.top {
display: flex;
// align-items: center;
justify-content: space-between;
border-bottom: 1.5px solid var(--theme-bg);
.input {
border-radius: 8px;
display: flex;
width: 145px;
height: 35px;
align-items: center;
}
.date-picker {
width: 360px;
}
.ant-select,
.ant-calendar-picker,
.ant-input {
width: 100%;
}
:deep(.ant-select-selection--single .ant-select-selection__rendered) {
height: 35px !important;
line-height: 35px !important;
}
.label {
width: 80px;
// margin-right: 20px;
color: var(--theme-text-default);
}
.top-right,
.top-right-column {
.ant-btn {
border-radius: 8px !important;
color: var(--theme-text2) !important;
font-weight: 700;
font-size: 14px !important;
display: flex;
justify-content: center;
align-items: center;
border-radius: 2px;
height: 35px !important;
line-height: 35px !important;
padding: 7px 10px !important;
border: none;
&.ant-btn-reset {
background-color: var(--theme-btn3) !important;
}
&.ant-btn-search {
background-color: var(--theme-btn1) !important;
}
.iconfont {
color: #fff;
font-size: 18px;
margin-right: 15px;
cursor: pointer;
margin-right: 8px !important;
}
}
}
.top-right {
// width: 10%;
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
.ant-btn {
&.ant-btn-reset {
margin-left: 10px !important;
}
}
}
.top-right-column {
display: block;
.ant-btn {
&.ant-btn-reset {
margin-top: 10px !important;
}
}
}
.top-left {
// width: 1200px;
display: flex;
flex-wrap: wrap;
align-items: center;
.item {
display: flex;
align-items: center;
margin-bottom: 15px;
margin-right: 20px;
}
}
}
.bottom {
display: flex;
margin-top: 15px;
margin-bottom: 15px;
justify-content: space-between;
align-items: center;
.button{
margin-left: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,176 @@
<template>
<div class="device">
<div class="device-item" v-for="item in 8" :key="item">
<div class="item-header">
<div style="display: flex;width: 50%;">
<div class="icon-bg"></div>
<div class="title">
<span class="number">521245786665412</span>
<span class="name">逆变器1</span>
<span class="number type">逆变器</span>
</div>
</div>
<div class="status">
<div class="status-item">
<span>在线</span>
<span class="text">在线状态</span>
</div>
<div class="status-item">
<span>在线</span>
<span class="text">故障状态</span>
</div>
<div class="status-item">
<span>在线</span>
<span class="text">工作状态</span>
</div>
</div>
</div>
<div class="item-content">
<div v-for="info in chunengInfo" :key="info.key">
<span class="text">{{ info.label }}</span>
<a-button v-if="info.key === 'realTimeData'" type="primary" size="small" @click="openModal">查看</a-button>
<span v-else class="value" >{{ info.value }}</span>
</div>
</div>
</div>
<a-modal v-model:open="modalOpen" @ok="handleOk" width="800px">
<!-- <p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p> -->
</a-modal>
</div>
</template>
<script>
export default {
name: '',
components: {},
props: {},
data() {
return {
modalOpen:false,
chunengInfo: [
{label:'运行模式',key:'operationMode',value:'并网运行'},
{label:'电池储能容量',key:'batteryCapacity',value:'100kWh'},
{ label: '实时电压', key: 'voltage', value: '232.5V' },
{ label: '功率因数', key: 'powerFactor', value: '0.95' },
{ label: '实时电流', key: 'current', value: '0.01A' },
{ label: '额定电压', key: 'ratedVoltage', value: '232.5V' },
{ label: '实时功率', key: 'power', value: '0.01kW' },
{ label: '额定电流', key: 'ratedCurrent', value: '0.01A' },
{ label: '实时数据', key: 'realTimeData', value: '0.01kWh' },
{ label: '额定功率', key: 'ratedPower', value: '0.01kW' },
{ 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() {},
methods: {
openModal(){
this.modalOpen=true;
},
handleOk(){
this.modalOpen=false;
}
}
}
</script>
<style lang="scss" scoped>
.device {
width: 100%;
height: 100%;
margin-left: 20px;
display: grid;
grid-gap: 20px;
grid-template-columns: 1fr 1fr 1fr;
overflow-y: auto;
.device-item {
// width: 410px;
// height: 340px;
border-radius: 15px;
background: $bg2-color;
padding: 15px;
.item-header {
display: flex;
align-items: center;
height: 70px;
color: #fff;
justify-content: space-between;
.icon-bg {
width: 76px;
height: 72px;
border-radius: 6px;
background: linear-gradient(90deg, #3dfefa 0%, #2a82e4 2.96%, #27a188 100%),
linear-gradient(90deg, #3dfefa 0%, #01dfef 2.96%, #08a5ff 100%);
}
.title {
display: flex;
flex-direction: column;
justify-content: space-around;
margin-left: 15px;
}
.number {
font-size: 12px;
}
.name {
font-size: 14px;
}
.type {
color: #08a5ff;
}
.status {
display: flex;
font-size: 14px;
height: 100%;
width: 50%;
justify-content: space-between;
&-item {
display: flex;
flex-direction: column;
text-align: center;
justify-content: space-evenly;
span:first-child {
font-weight: 700;
}
.text {
color: $text-color;
}
}
}
}
.item-content {
grid-template-columns: 1fr 1fr;
color: #fff;
display: grid;
line-height: 45px;
margin-top: 15px;
padding: 0 10px;
.value {
font-weight: 700;
}
}
.text {
color: $text-color;
}
.video {
margin-top: 10px;
}
}
}
.environment {
width: 200px;
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<div class="videos">
<div class="video-item" v-for="item in 8" :key="item">
<div class="title">
<span>xxxxz监控点</span>
<img
src="@/assets/images/fillScreen.png"
alt=""
width="23px"
style="margin-left: 10px; cursor: pointer"
/>
</div>
<div class="video">
<video
src="https://media.w3.org/2010/05/sintel/trailer_hd.mp4"
controls="controls"
width="100%"
height="100%"
></video>
</div>
</div>
</div>
<div class="environment">
<div class="item">
<div class="title">环境温湿度信息</div>
<img src="@/assets/images/titleLine.png" alt="" width="100%" />
<div class="content">
<div class="header">
<div>点位</div>
<div>温度</div>
<div>湿度</div>
</div>
<div class="row">
<div>#1</div>
<div>20 </div>
<div>20%</div>
</div>
</div>
</div>
<div class="item" style="margin-top: 40px">
<div class="title">消防信息</div>
<img src="@/assets/images/titleLine.png" alt="" width="100%" />
<div class="content">
<div class="header">
<div>点位</div>
<div>烟感状态</div>
</div>
<div class="row" v-for="value in 6" :key="value">
<div>#{{ value }}</div>
<div>xxx</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: '',
components: {},
props: {},
data() {
return {}
},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
.videos {
width: calc(100% - 240px);
margin-left: 20px;
display: grid;
grid-gap: 20px;
grid-template-columns: 1fr 1fr 1fr;
overflow-y: auto;
.video-item {
// width: 410px;
// height: 340px;
border-radius: 15px;
background: $bg2-color;
padding: 10px;
.title {
display: flex;
align-items: center;
font-size: 24px;
font-weight: 700;
color: #fff;
}
.video {
margin-top: 10px;
}
}
}
.environment {
width: 220px;
margin-left: 10px;
color: #fff;
.title {
font-size: 24px;
font-weight: 700;
}
.content {
margin-top: 15px;
border-radius: 6px;
border: 1px solid $border-color;
font-size: 18px;
font-weight: 700;
.header {
height: 40px;
background: linear-gradient(90deg, #3dfefa33 0%, #00fffb33 50.17%, #3dfefa33 100%), #3dfefa33;
}
.header,
.row {
display: flex;
div {
flex: 1; /* 每列平分宽度 */
text-align: center; /* 文字水平居中 */
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中(如果需要) */
padding: 10px; /* 可选:添加内边距 */
}
}
}
}
</style>

View File

@@ -1,10 +1,10 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/main'
},
export const routes = [
// {
// path: '/',
// redirect: '/main'
// },
{
path: '/login',
name: 'login',
@@ -22,7 +22,66 @@ const routes = [
},
{
path: 'monitor',
component: () => import(/* webpackChunkName: "monitor" */ '@/views/sub/monitor.vue')
name: 'monitor',
title: '运行监控',
component: () => import(/* webpackChunkName: "monitor" */ '@/views/monitor.vue')
},
{
path: 'system',
name: 'system',
redirect: '/system/policy',
component: () => import(/* webpackChunkName: "system" */ '@/views/system/index.vue'),
children: [
{
path: 'user',
name: 'user',
title: '用户管理',
component: () => import(/* webpackChunkName: "system" */ '@/views/system/user.vue')
},
{
name: 'role',
path: 'role',
title: '角色管理'
// component: () => import(/* webpackChunkName: "system" */ '@/views/system/role.vue')s
},
{
name: 'permission',
path: 'permission',
title: '权限管理'
},
{
name: 'station',
path: 'station',
title: '场站管理'
},
{
name: 'service',
path: 'service',
title: '服务管理'
},
{
path: 'policy',
name: 'policy',
title: '策略管理',
component: () => import(/* webpackChunkName: "system" */ '@/views/system/policy.vue')
},
{
name: 'device',
path: 'device',
title: '设备管理'
},
{
name: 'log',
path: 'log',
title: '告警日志'
},
{
name: 'syslog',
path: 'syslog',
title: '系统日志'
}
]
}
]
}

View File

@@ -1,14 +1,49 @@
$border-color:#12FBFF;
$border-color: #12fbff;
$btn-confirm: #1C918A;
$btn-del:#D43030;
//级联器样式
.ant-cascader{
.ant-select-selector{
.ant-cascader {
.ant-select-selector {
background: none !important;
border: 1px solid $border-color !important;
border: 1px solid $border-color !important;
}
.ant-select-arrow {
color: $border-color;
}
.ant-select-selection-placeholder{
color: #ffffff3b;
}
}
.ant-select-arrow{
color: $border-color;
//按钮样式
// .ant-btn{
// padding: 4px 8px;
// }
.ant-btn-primary{
background: $btn-confirm;
&:hover{
background: $btn-confirm;
opacity: 0.8;
}
&:active{
background: #0f6f6a;
}
}
.btn-del{
background: $btn-del;
&:hover{
background: $btn-del;
opacity: 0.8;
}
&:active{
background: $btn-del;
}
}
//modal样式
.ant-modal .ant-modal-content{
background-image: url('@/assets/images/modalBg.png');
background-size: 100% 100%;
background-color: #ffffff00 !important;
border-radius: 0;
}

View File

@@ -1 +1,5 @@
$border-color:#12FBFF
$bg1-color:#052f4d;
$bg2-color:#2169c31f;
$bg3-color:#00d2ff1f;
$text-color:#A6B8DD

View File

@@ -1 +1,34 @@
::-webkit-scrollbar {
width: 5px;
height: 6px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-track-piece {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
cursor: pointer;
border-radius: 4px;
background: rgba(144, 147, 153, 0.3);
}
::-webkit-scrollbar-thumb:hover {
background: #989eac;
}
::-webkit-scrollbar-corner {
display: none;
}
::-webkit-resizer {
display: none;
}

View File

@@ -1,11 +1,32 @@
<template>
<div class="main">
<div class="header"></div>
<div class="page">
<router-view/>
<div class="subMenu" v-if="subMenu.length > 0">
<div
class="subItem"
v-for="subItem in subMenu"
:key="subItem.name"
@click="subMenuClick(subItem)"
:class="subCurrentKey == subItem.path ? 'active' : ''"
>
{{ subItem.title }}
</div>
</div>
<div :class="[subMenu.length > 0 ? 'subcontent' : 'content']">
<router-view />
</div>
</div>
<div class="menu">
<div v-for="menu in menuList" :key="menu.name" class="menu-item">
<div
v-for="menu in menuList"
:key="menu.name"
class="menu-item"
@click="menuClick(menu)"
:class="currentKey == menu.path ? 'active' : ''"
>
<i :class="menu.icon"></i>
{{ menu.name }}
</div>
@@ -21,29 +42,83 @@ export default {
components: {},
data() {
return {
currentKey: '',
subCurrentKey: '',
menuList: [
{
name: '系统总览',
icon: 'icon-xitongguanli'
},
{
name: '运行监控',
path: '/monitor'
},
{
name: '系统管理',
icon: 'icon-xitongguanli',
path: '/system',
children: [
{
name: '用户管理',
icon: 'icon-yonghuguanli',
children: [
{
name: '用户列表',
icon: 'icon-yonghuguanli',
path: '/user/list'
}
]
path: '/user'
},
{
name: '角色管理',
path: '/role'
},
{
name: '权限管理',
icon: 'icon-caidanguanli'
},
{
name: '场站管理',
icon: 'icon-caidanguanli'
},
{
name: '服务管理',
icon: 'icon-bumenguanli'
},
{
name: '策略管理',
path: '/policy'
},
{
name: '设备管理',
icon: 'icon-rizhiguanli'
},
{
name: '告警日志',
icon: 'icon-rizhiguanli'
},
{
name: '系统日志',
icon: 'icon-rizhiguanli'
}
]
}
]
],
subMenu: []
}
},
mounted() {
this.initRoute()
},
methods: {
initRoute() {
console.log(this.$route,this.$router)
this.subMenu = this.$route.matched[1].children || []
this.currentKey = '/' + this.$route.fullPath.split('/')[1]
this.subCurrentKey = this.$route.fullPath.split('/')[2]
console.log(this.subCurrentKey)
},
menuClick(menu) {
this.currentKey = menu.path
this.subMenu = menu.children || []
this.$router.push(menu.path)
},
subMenuClick(subMenu) {
this.subCurrentKey = subMenu.path
this.$router.push(subMenu.path)
}
}
}
@@ -56,18 +131,44 @@ export default {
background-size: 100% 100%;
background-repeat: no-repeat;
}
.header {
.header {
width: 100%;
height: 80px;
border: 1px solid red;
height: 70px;
}
.page{
width: calc(100% - 20px);
height: calc(100% - 80px - 15px - 47px - 60px);
margin: 40px 10px 20px 10px;
border-radius: 20px;
// background: #052F4D;
.subMenu {
display: flex;
color: #fff;
margin-left: 10px;
padding-bottom: 20px;
.subItem {
width: 96px;
height: 36px;
line-height: 36px;
text-align: center;
background: #132347;
border: 1px solid #2169c3;
border-radius: 8px;
margin-right: 15px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.active {
background: #27a188;
}
}
.page {
width: calc(100% - 20px);
height: calc(100% - 70px - 65px - 40px);
margin: 20px 10px;
border-radius: 20px;
}
.content{
height: 100%;
}
.subcontent{
height: calc(100% - 46px);
}
.menu {
position: absolute;
@@ -86,6 +187,11 @@ export default {
color: #fff;
font-size: 20px;
font-weight: 700;
cursor: pointer;
}
.active {
border: 1px solid $border-color;
color: #01b3cd;
}
}
</style>

188
web/src/views/monitor.vue Normal file
View File

@@ -0,0 +1,188 @@
<template>
<div class="monitor">
<div class="search">
<div class="left">
<div class="search-item">
<span>场站切换</span>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" />
</div>
</div>
<div class="right">
<div class="search-item">
<span>运行模式</span>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" />
</div>
<div class="search-item">
<span>策略名称</span>
<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 class="content">
<div class="stations">
<div class="station-item" v-for="station in stations" :key="station.name" @click="()=>currentKey=station.name" :class="currentKey==station.name?'active':''">
<span class="name">{{ station.name }}</span>
<span class="des">总功率{{ station.power }} W</span>
<span class="des">数量{{ station.num }}</span>
</div>
</div>
<div class="container">
<device v-if="stationType" />
<videos v-else />
</div>
</div>
</div>
</template>
<script>
import device from '@/components/monitor/device.vue'
import videos from '@/components/monitor/videos.vue'
export default {
name: 'MonitorView',
components: {
device,
videos
},
data() {
return {
currentKey:'储能系统1',
stationType: 1,
value: [],
options: [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake'
}
]
}
]
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen'
}
]
}
]
}
],
stations: [
{
name: '储能系统1',
power: 60,
num: 62
},
{
name: '储能系统2',
power: 60,
num: 62
}
// {
// name: "储能系统3",
// power: 60,
// num: 62
// },
// {
// name: "储能系统4",
// }
]
}
}
}
</script>
<style scoped lang="scss">
@import url(@/style/color.scss);
.monitor {
width: 100%;
height: 100%;
padding: 20px;
background: $bg1-color;
border-radius: 15px;
.search {
display: flex;
justify-content: space-between;
.search-item {
span {
margin-right: 20px;
}
color: #fff;
margin-left: 30px;
&:first-child {
margin-left: 0;
}
}
.left,
.right {
display: flex;
}
}
.content {
width: 100%;
height: calc(100% - 32px - 20px);
margin-top: 20px;
display: flex;
justify-content: space-between;
.stations {
min-width: 155px;
max-width: 235px;
width: 13%;
height: 100%;
border-radius: 12px;
background: $bg2-color;
padding: 15px 0;
overflow-y: auto;
.station-item {
width: calc(100% - 30px);
margin: 0 15px 15px 15px;
border-radius: 12px;
display: flex;
flex-direction: column;
color: #fff;
padding: 10px 15px;
cursor: pointer;
.name {
font-size: 20px;
font-weight: 700;
line-height: 50px;
}
.des {
font-size: 14px;
font-weight: 600;
line-height: 40px;
}
}
.active{
background: $bg3-color;
}
}
.container {
width: 87%;
display: flex;
}
}
}
</style>

View File

@@ -1,27 +0,0 @@
<template>
<div class="monitor">
<div class="search">
<div class="left">
<div class="search-item">
<span>场站切换</span>
<a-cascader v-model:value="value" :options="options" placeholder="Please select" />
</div>
</div>
<div class="right"></div>
</div>
</div>
</template>
<script setup>
</script>
<style scoped lang="scss">
.monitor{
padding: 10px;
.search{
display: flex;
justify-content: space-between;
}
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div class="system">
<router-view/>
</div>
</template>
<script>
export default {
name: '',
components:{
},
props: {
},
data() {
return {
}
},
mounted() {
},
methods:{
},
}
</script>
<style lang="scss" scoped>
.system{
width: 100%;
height: 100%;
background: $bg1-color;
border-radius: 15px;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div class="policy">
<searchBox :btn-option-list="btnOptionList" @onSearch="onSearch" @operateForm="operateForm"/>
</div>
</template>
<script>
import searchBox from '@/components/SearchBox.vue'
export default {
name: '',
components:{searchBox
},
props: {
},
data() {
return {
btnOptionList:[
{label:'新增',icon:'icon-tianjia',type:'add'},
{label:'删除',icon:'icon-tianjia',type:'del'}
]
}
},
mounted() {
},
methods:{
onSearch(data){
console.log(data)
},
operateForm(type){
console.log(type )
},
}
}
</script>
<style lang="scss" scoped>
.policy{
width: 100%;
height: 100%;
padding: 0 15px;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div >
</div>
</template>
<script>
export default {
name: '',
components:{
},
props: {
},
data() {
return {
}
},
mounted() {
},
methods:{
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -38,8 +38,8 @@ module.exports = defineConfig({
loaderOptions: {
scss: {
additionalData: `
@use "~@/style/color.scss";
@use "~@/style/antd.scss";
@import "@/style/color.scss";
@import "@/style/antd.scss";
` //在每个 .scss 文件顶部自动添加这行代码,无需手动导入
}
},