合并代码

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

@@ -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>