This commit is contained in:
2026-02-17 21:44:12 +08:00
parent c7c1b416ee
commit 9272a0162a
9 changed files with 507 additions and 291 deletions

View File

@ -181,6 +181,15 @@ export function createTicketNo(data) {
}) })
} }
// 告警确认关闭
export function closeAlarm(data) {
return request({
url: `/ems/siteAlarm/closeAlarm`,
method: 'post',
data
})
}
function getFieldNameByCode(fieldCode) { function getFieldNameByCode(fieldCode) {
const raw = String(fieldCode || '').trim() const raw = String(fieldCode || '').trim()
if (!raw) return '' if (!raw) return ''

View File

@ -100,6 +100,9 @@ import {mapGetters} from "vuex"
onSubmit(){ onSubmit(){
this.$emit('submitSite',this.id) this.$emit('submitSite',this.id)
}, },
emitSitesLoaded() {
this.$emit('sitesLoaded', this.siteList || [])
},
setDefaultSite(){ setDefaultSite(){
const defaultSite = this.defaultSiteId const defaultSite = this.defaultSiteId
if(defaultSite && this.siteList.find(item=>item.siteId === defaultSite)){ if(defaultSite && this.siteList.find(item=>item.siteId === defaultSite)){
@ -112,6 +115,7 @@ import {mapGetters} from "vuex"
getList(){ getList(){
return getAllSites().then(response => { return getAllSites().then(response => {
this.siteList = response.data || [] this.siteList = response.data || []
this.emitSitesLoaded()
this.setDefaultSite() this.setDefaultSite()
}).finally(() => {this.loading=false;this.searchLoading=false}) }).finally(() => {this.loading=false;this.searchLoading=false})
} }
@ -127,6 +131,7 @@ import {mapGetters} from "vuex"
}) })
}else{ }else{
this.siteList = this.zdList this.siteList = this.zdList
this.emitSitesLoaded()
this.loading=false this.loading=false
this.searchLoading=false this.searchLoading=false
this.setDefaultSite() this.setDefaultSite()

View File

@ -94,13 +94,21 @@
<el-table-column <el-table-column
label="工单" label="工单"
fixed="right" fixed="right"
width="250" width="320"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini" v-if="scope.row.ticketNo" @click="toTicket"> <el-button type="text" size="mini" v-if="scope.row.ticketNo" @click="toTicket">
已生成工单(工单号:{{ scope.row.ticketNo }}) 已生成工单(工单号:{{ scope.row.ticketNo }})
</el-button> </el-button>
<el-button type="primary" size="mini" v-else @click="createTicket(scope.row.id)">生成工单</el-button> <el-button type="primary" size="mini" v-else @click="createTicket(scope.row.id)">生成工单</el-button>
<el-button
v-if="scope.row.status !== '1'"
type="success"
size="mini"
style="margin-left: 8px;"
@click="closeAlarmRecord(scope.row.id)">
确认关闭
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -123,7 +131,7 @@
<script> <script>
import {createTicketNo, getAlarmDetailList} from '@/api/ems/dzjk' import {closeAlarm, createTicketNo, getAlarmDetailList} from '@/api/ems/dzjk'
import {getDeviceList} from '@/api/ems/site' import {getDeviceList} from '@/api/ems/site'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId"; import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import {formatDate} from '@/filters/ems' import {formatDate} from '@/filters/ems'
@ -169,6 +177,23 @@ export default {
this.loading = false this.loading = false
}) })
}, },
//确认关闭告警
closeAlarmRecord(id) {
this.$confirm('确认关闭该故障告警吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true
closeAlarm({id}).then(() => {
this.$message.success('关闭成功')
this.getData()
}).finally(() => {
this.loading = false
})
}).catch(() => {
})
},
// 判断是否是同一天 // 判断是否是同一天
isSameDay(day1, day2) { isSameDay(day1, day2) {
const date1 = new Date(day1), date2 = new Date(day2) const date1 = new Date(day1), date2 = new Date(day2)
@ -250,4 +275,3 @@ export default {
} }
} }
</script> </script>

View File

@ -89,95 +89,6 @@
</div> </div>
</div> </div>
</div> </div>
<el-table
:data="baseInfo.batteryDataList"
class="common-table"
max-height="500"
stripe
style="width: 100%;margin-top:25px;">
<el-table-column
label="簇号"
prop="clusterId">
</el-table-column>
<el-table-column
label="簇电压"
>
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'clusterVoltage', '簇电压')">{{ scope.row.clusterVoltage }} V</span>
</template>
</el-table-column>
<el-table-column
label="簇电流">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'clusterCurrent', '簇电流')">{{ scope.row.clusterCurrent }} A</span>
</template>
</el-table-column>
<el-table-column
label="簇SOC">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'currentSoc', '当前SOC')">{{ scope.row.currentSoc }} %</span>
</template>
</el-table-column>
<el-table-column
label="单体最高电压"
prop="maxVoltage">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'maxCellVoltage', '最高单体电压')">{{
scope.row.maxCellVoltage
}} V</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="maxCellVoltageId">
</el-table-column>
<el-table-column
label="单体最低电压"
prop="minVoltage">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'minCellVoltage', '最低单体电压')">{{
scope.row.minCellVoltage
}} V</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="minCellVoltageId">
</el-table-column>
<el-table-column
label="单体最高温度">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'maxCellTemp', '最高单体温度')">{{
scope.row.maxCellTemp
}} &#8451;</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="maxCellTempId">
</el-table-column>
<el-table-column
label="单体最低温度"
prop="minTemperature">
<template slot-scope="scope">
<span class="pointer"
@click="handleClusterFieldClick(scope.row, 'minCellTemp', '最低单体温度')">{{
scope.row.minCellTemp
}} &#8451;</span>
</template>
</el-table-column>
<el-table-column
label="电池号码"
prop="minCellTempId">
</el-table-column>
</el-table>
</el-card> </el-card>
</div> </div>
<el-dialog <el-dialog

View File

@ -37,8 +37,8 @@
>{{ pcsItem.deviceName }}</span >{{ pcsItem.deviceName }}</span
> >
<div class="info"> <div class="info">
<div> <div v-if="(($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[pcsItem.communicationStatus]">
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[pcsItem.communicationStatus] || '-' }} {{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[pcsItem.communicationStatus] }}
</div> </div>
<div>数据更新时间{{ pcsItem.dataUpdateTime }}</div> <div>数据更新时间{{ pcsItem.dataUpdateTime }}</div>
</div> </div>

View File

@ -40,6 +40,10 @@
prop="siteName" prop="siteName"
label="站点名称"> label="站点名称">
</el-table-column> </el-table-column>
<el-table-column
prop="siteShortName"
label="站点简称">
</el-table-column>
<el-table-column <el-table-column
prop="siteAddress" prop="siteAddress"
label="站点地址" label="站点地址"
@ -98,6 +102,13 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="站点简称" prop="siteShortName">
<el-input v-model.trim="siteForm.siteShortName" placeholder="请输入站点简称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运营时间" prop="runningTime"> <el-form-item label="运营时间" prop="runningTime">
@ -160,6 +171,7 @@ const emptySiteForm = () => ({
id: undefined, id: undefined,
siteId: '', siteId: '',
siteName: '', siteName: '',
siteShortName: '',
siteAddress: '', siteAddress: '',
runningTime: '', runningTime: '',
installPower: '', installPower: '',
@ -252,6 +264,7 @@ export default {
id: row.id, id: row.id,
siteId: row.siteId || '', siteId: row.siteId || '',
siteName: row.siteName || '', siteName: row.siteName || '',
siteShortName: row.siteShortName || '',
siteAddress: row.siteAddress || '', siteAddress: row.siteAddress || '',
runningTime: row.runningTime || '', runningTime: row.runningTime || '',
installPower: row.installPower || '', installPower: row.installPower || '',

View File

@ -1,95 +1,163 @@
<template> <template>
<div id="zddtChart" style="height: 100%;width:100%"></div> <div class="map-wrapper">
<div ref="mapRef" class="map-canvas"></div>
<div v-if="!hasPoint" class="map-empty">暂无站点坐标</div>
</div>
</template> </template>
<script> <script>
import * as echarts from 'echarts' const TDT_SCRIPT_ID = 'tianditu-js-sdk'
import resize from '@/mixins/ems/resize' let tdtScriptLoading = null
import china from '@/data/ems/china.json'//中国地图数据
import 'echarts/lib/chart/map'; function loadTdtScript(tk) {
echarts.registerMap('china', { geoJSON: china }); //注册可用地图 if (window.T) return Promise.resolve(window.T)
if (tdtScriptLoading) return tdtScriptLoading
tdtScriptLoading = new Promise((resolve, reject) => {
const oldScript = document.getElementById(TDT_SCRIPT_ID)
if (oldScript) {
oldScript.addEventListener('load', () => resolve(window.T))
oldScript.addEventListener('error', () => reject(new Error('天地图脚本加载失败')))
return
}
const script = document.createElement('script')
script.id = TDT_SCRIPT_ID
script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${tk}`
script.async = true
script.onload = () => {
if (window.T) resolve(window.T)
else reject(new Error('天地图对象未初始化'))
}
script.onerror = () => reject(new Error('天地图脚本加载失败'))
document.body.appendChild(script)
})
return tdtScriptLoading
}
export default { export default {
mixins: [resize],
data() { data() {
return { return {
chart: null, hasPoint: false,
map: null,
overlays: [],
pendingPayload: null,
mapConfig: {
zoom: 12,
tk: '01e99ab4472430e1c7dbfe4b5db99787'
}
} }
}, },
mounted() { mounted() {
this.$nextTick(() => { this.initMap()
this.initChart()
})
}, },
beforeDestroy() { beforeDestroy() {
if (!this.chart) { this.clearOverlays()
return this.map = null
}
this.chart.dispose()
this.chart = null
}, },
methods: { methods: {
initChart() { async initMap() {
// ECharts 默认有提供了一个简单的加载动画。只需要调用 showLoading 方法显示。数据加载完成后再调用 hideLoading 方法隐藏加载动画。 try {
this.chart = echarts.init(document.querySelector('#zddtChart')) await loadTdtScript(this.mapConfig.tk)
if (!this.$refs.mapRef || !window.T) return
this.map = new window.T.Map(this.$refs.mapRef)
const defaultCenter = new window.T.LngLat(104.1, 35.9)
this.map.centerAndZoom(defaultCenter, 5)
if (this.pendingPayload) {
this.renderPayload(this.pendingPayload)
}
} catch (e) {
// 页面可继续使用,地图只显示空态
this.hasPoint = false
}
}, },
setOption(data) { normalizePoint(site = {}) {
this.chart.setOption({ const name = site.siteName || site.name || ''
color:['#FFBD00'], const value = site.value || site.siteLocation || []
backgroundColor: 'transparent', //背景色 const lonSource = site.longitude !== undefined && site.longitude !== null ? site.longitude : value[0]
geo: { //地理坐标系组件 地理坐标系组件用于地图的绘制,支持在地理坐标系上绘制 const latSource = site.latitude !== undefined && site.latitude !== null ? site.latitude : value[1]
map: 'china', //地图类型 这儿展示的是中国地图 const lon = Number(lonSource)
aspectScale: 0.85, const lat = Number(latSource)
selectedMode: "single",// 开启单选 if (!lon || !lat) return null
label: { return { name, lon, lat }
show: true, //是否显示标签 此处指是否显示地图上的地区名字 },
color: '#ffffff', clearOverlays() {
fontSize: 12 if (!this.map || !this.overlays.length) return
}, this.overlays.forEach(item => this.map.removeOverLay(item))
roam: true, //是否开启鼠标缩放和平移漫游 this.overlays = []
itemStyle: { },
areaColor: "#03365b", renderPayload(payload = {}) {
borderColor: "#4bf3f9", const isArrayPayload = Array.isArray(payload)
shadowColor: '#03365b', //阴影颜色 const selectedRaw = isArrayPayload ? ((payload || [])[0] || {}) : (payload.selected || {})
shadowOffsetX: 0, //阴影偏移量 const sitesRaw = isArrayPayload ? [] : (payload.sites || [])
shadowOffsetY: 0, //阴影偏移量 const selected = this.normalizePoint(selectedRaw)
}, const points = (Array.isArray(sitesRaw) ? sitesRaw : [])
emphasis: { .map(item => this.normalizePoint(item))
label: { .filter(Boolean)
show: true, if (selected && !points.find(item => item.lon === selected.lon && item.lat === selected.lat)) {
color: '#ffffff', points.push(selected)
}, }
itemStyle: { this.clearOverlays()
areaColor: "#0f5d9d", this.hasPoint = points.length > 0
} if (!this.map || !points.length || !window.T) return
}
}, const viewPoints = []
series: [ points.forEach(item => {
{ const lngLat = new window.T.LngLat(item.lon, item.lat)
type: "effectScatter", const marker = new window.T.Marker(lngLat)
coordinateSystem: "geo", this.map.addOverLay(marker)
showEffectOn: "render", this.overlays.push(marker)
data, viewPoints.push(lngLat)
rippleEffect: {
brushType: "stroke",
scale: 5,
period: 2, // 秒数
},
symbolSize: 12,
clickable: false,
zlevel: 1,
label: {
formatter: "{b}",
position: "right",
show: true,
},
}
]
}) })
if (selected && selected.name) {
const label = new window.T.Label({
text: selected.name,
position: new window.T.LngLat(selected.lon, selected.lat),
offset: new window.T.Point(8, -34)
})
this.map.addOverLay(label)
this.overlays.push(label)
}
if (viewPoints.length === 1) {
this.map.centerAndZoom(viewPoints[0], this.mapConfig.zoom)
} else {
this.map.setViewport(viewPoints)
}
},
setOption(payload = {}) {
this.pendingPayload = payload
if (!this.map) return
this.renderPayload(payload)
} }
} }
} }
</script> </script>
<style scoped lang="scss">
.map-wrapper {
position: relative;
width: 100%;
height: 100%;
min-height: 0;
border-radius: 8px;
overflow: hidden;
background: #f5f7fa;
}
.map-canvas {
width: 100%;
height: 100%;
}
.map-empty {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
background: rgba(245, 247, 250, 0.9);
pointer-events: none;
}
</style>

View File

@ -1,33 +1,55 @@
<template> <template>
<div class="ems-dashboard-editor-container" v-loading="loading"> <div class="ems-dashboard-editor-container" v-loading="loading">
<zd-info></zd-info> <zd-info></zd-info>
<div class="ems-content-container "> <div class="ems-content-container">
<div class="map-container"> <div class="map-container">
<map-chart ref="mapChart"/> <div class="site-cards-wrapper" v-if="allSites.length > 0">
</div> <button
<div class="zd-msg-container"> class="site-cards-arrow site-cards-arrow--left"
<div class="zd-msg-top"> type="button"
<zd-select ref="zdSelect" @submitSite="submitSite"></zd-select> :disabled="!canScrollLeft"
<el-card class="common-card-container"> @click="scrollSiteCards('left')"
<div slot="header"> >
<span class="card-title">基本信息</span> <i class="el-icon-arrow-left"></i>
<el-button style="float: right; padding: 3px 0" type="text" size="small" @click="toDzjk">查看详情</el-button> </button>
<div ref="siteCards" class="site-cards" @scroll="updateScrollButtons">
<div
v-for="item in allSites"
:key="item.siteId"
class="site-card"
:class="{ active: isSameSite(item.siteId, singleSiteId) }"
@click="submitSite(item.siteId)"
>
<div class="site-card-name">{{ item.siteName || '-' }}</div>
<div class="site-card-info-row">
<span class="site-card-label">电站位置</span>
<span class="site-card-value site-card-value--address">{{ formatSiteCardField(item.siteAddress) }}</span>
</div>
<div class="site-card-info-row">
<span class="site-card-label">投运时间</span>
<span class="site-card-value">{{ formatSiteCardDate(item.runningTime) }}</span>
</div>
<div class="site-card-info-row">
<span class="site-card-label">装机功率(MW)</span>
<span class="site-card-value">{{ formatSiteCardField(item.installPower) }}</span>
</div>
<div class="site-card-info-row">
<span class="site-card-label">装机容量(MW)</span>
<span class="site-card-value">{{ formatSiteCardField(item.installCapacity) }}</span>
</div>
</div> </div>
<div class="single-zd-name">{{singleSiteName}}</div> </div>
<!-- 四个方块--> <button
<el-row :gutter="14"> class="site-cards-arrow site-cards-arrow--right"
<el-col :span="12" class="single-square-box-container" v-for="(item,index) in singleZdSqaure" :key="index+'singleSquareBox'"> type="button"
<single-square-box :data="item"></single-square-box> :disabled="!canScrollRight"
</el-col> @click="scrollSiteCards('right')"
</el-row> >
<!-- 基本信息 --> <i class="el-icon-arrow-right"></i>
<el-descriptions class="single-zd-info-container" :column="1" > </button>
<el-descriptions-item v-for="(item,index) in singleZdInfo" :key="index+'singleZdInfo'" :label="item.title">{{item.value | formatNumber }}</el-descriptions-item> </div>
</el-descriptions> <div class="map-view">
<!-- echarts柱状图--> <map-chart ref="mapChart"/>
<bar-chart ref="barChart"></bar-chart>
</el-card>
</div> </div>
</div> </div>
</div> </div>
@ -36,94 +58,101 @@
<script> <script>
import ZdInfo from '@/components/Ems/ZdBaseInfo/index.vue' import ZdInfo from '@/components/Ems/ZdBaseInfo/index.vue'
import ZdSelect from '@/components/Ems/ZdSelect/index.vue'
import SingleSquareBox from '@/components/Ems/SingleSquareBox/index.vue'
import BarChart from './BarChart.vue'
import MapChart from './MapChart.vue' import MapChart from './MapChart.vue'
import {getSingleSiteBaseInfo} from '@/api/ems/zddt' import { getAllSites } from '@/api/ems/zddt'
export default { export default {
components:{ZdSelect,ZdInfo,SingleSquareBox,BarChart,MapChart}, components: { ZdInfo, MapChart },
data() { data() {
return { return {
loading:false, loading: false,
singleSiteId:'', singleSiteId: '',
singleSiteName:'', singleSiteName: '',
singleSiteLocation:[], singleSiteLocation: [],
// 单个电站 四个方块数据 allSites: [],
singleZdSqaure:[ canScrollLeft: false,
{ canScrollRight: false
title:'今日充电kWh',
value:'',
bgColor:'#FFE5E5',
attr:'dayChargedCap'
},{
title:'累计充电kWh',
value:'',
bgColor:'#FFE5E5',
attr:'totalChargedCap'
},{
title:'今日放电kWh',
value:'',
bgColor:'#EEEBFF',
attr:'dayDisChargedCap'
},{
title:'累计放电kWh',
value:'',
bgColor:'#EEEBFF',
attr:'totalDisChargedCap'
}
],
// 单个电站 基本信息
singleZdInfo:[{
title:'电站位置',
value:'',
attr:'siteAddress'
},{
title:'投运时间',
value:'',
attr:'runningTime'
},{
title:'装机功率(MW)',
value:'',
attr:'installPower'
},{
title:'装机容量(MW)',
value:'',
attr:'installCapacity',
}]
} }
}, },
mounted() {
this.loadSites()
window.addEventListener('resize', this.updateScrollButtons)
},
beforeDestroy() {
window.removeEventListener('resize', this.updateScrollButtons)
},
methods:{ methods:{
isSameSite(siteId, selectedId) {
return String(siteId) === String(selectedId)
},
formatSiteCardField(value) {
if (value === null || value === undefined || value === '') {
return '-'
}
return value
},
formatSiteCardDate(value) {
if (!value) {
return '-'
}
const text = String(value)
if (text.includes('T')) {
return text.slice(0, 10)
}
return text.length > 10 ? text.slice(0, 10) : text
},
loadSites() {
this.loading = true
getAllSites().then(response => {
this.allSites = response?.data || []
if (this.allSites.length > 0) {
this.submitSite(this.allSites[0].siteId)
} else {
this.updateMapMarkers()
}
this.$nextTick(() => {
this.updateScrollButtons()
})
}).finally(() => {
this.loading = false
})
},
updateScrollButtons() {
const container = this.$refs.siteCards
if (!container) {
this.canScrollLeft = false
this.canScrollRight = false
return
}
const maxScrollLeft = container.scrollWidth - container.clientWidth
this.canScrollLeft = container.scrollLeft > 0
this.canScrollRight = maxScrollLeft > 0 && container.scrollLeft < maxScrollLeft - 1
},
scrollSiteCards(direction) {
const container = this.$refs.siteCards
if (!container) {
return
}
const amount = Math.max(container.clientWidth * 0.8, 240)
const delta = direction === 'left' ? -amount : amount
container.scrollBy({ left: delta, behavior: 'smooth' })
},
updateMapMarkers(){
this.$refs.mapChart && this.$refs.mapChart.setOption({
selected: {name:this.singleSiteName,value:this.singleSiteLocation},
sites:this.allSites
})
},
// 站点选中 // 站点选中
submitSite(id){ submitSite(id){
if(this.singleSiteId === id){return console.log(`点击搜索按钮 搜索相同的站点id= ${id}不再调用获取基本信息接口`)}
this.loading=true
console.log('点击搜索按钮 选中的站点id',id)
this.singleSiteId = id this.singleSiteId = id
this.$refs.zdSelect.searchLoading = true const currentSite = this.allSites.find(item => this.isSameSite(item.siteId, id)) || {}
getSingleSiteBaseInfo(id).then(response => { this.singleSiteName = currentSite.siteName || ''
console.log('单个站点详情数据',response) this.singleSiteLocation = currentSite.siteLocation || []
const res = response?.data || {} if (!this.singleSiteLocation.length) {
this.singleSiteName = res?.siteName || ''//站点名称 this.singleSiteLocation = [currentSite.longitude, currentSite.latitude].filter(item => item !== undefined && item !== null)
this.singleSiteLocation = res?.siteLocation || []//站点坐标 }
this.singleZdSqaure.forEach(item=>{ this.$nextTick(() => {
item.value =res[item.attr] this.updateMapMarkers()
})
this.singleZdInfo.forEach(item=>{
item.value = res[item.attr]
})
this.$refs.barChart.setOption(res?.sevenDayDisChargeStats || [])
this.$refs.mapChart.setOption([{name:this.singleSiteName,value:this.singleSiteLocation}])
}).finally(() => {this.$refs.zdSelect.searchLoading = false;this.loading=false})
},
//跳转单站监控页面
toDzjk(){
this.$router.push({
path:'/dzjk',
query:{
siteId:this.singleSiteId,
}
}) })
} }
}, },
@ -134,29 +163,119 @@ export default {
.ems-content-container{ .ems-content-container{
display: flex; display: flex;
padding:24px; padding:24px;
padding-right: 0;
.map-container{ .map-container{
flex:auto; flex:auto;
} min-width: 0;
.zd-msg-container{ display: flex;
width: 500px; flex-direction: column;
padding: 24px; gap: 12px;
.single-zd-name{ .site-cards-wrapper{
font-weight: 500; display: flex;
line-height: 23px; align-items: center;
color: #FFBD00; gap: 8px;
font-size: 16px; .site-cards-arrow{
margin-bottom: 24px; width: 28px;
height: 28px;
border: none;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 0 0 1px #dcdfe6 inset;
color: #606266;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all .2s ease;
&:hover:not(:disabled){
color: #2b74ff;
box-shadow: 0 0 0 1px #2b74ff inset;
}
&:disabled{
cursor: not-allowed;
color: #c0c4cc;
box-shadow: 0 0 0 1px #ebeef5 inset;
}
}
} }
.single-square-box-container{ .site-cards{
height: 78px; display: flex;
box-sizing: border-box; gap: 10px;
margin-bottom: 10px; overflow-x: auto;
padding-bottom: 2px;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar{
display: none;
}
.site-card{
flex: 0 0 360px;
height: 166px;
border-radius: 8px;
background: #ffffff;
border: 1px solid #e4e7ed;
padding: 10px;
box-sizing: border-box;
cursor: pointer;
transition: all .2s ease;
.site-card-name{
font-size: 14px;
font-weight: 600;
color: #303133;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.site-card-info-row{
display: flex;
align-items: flex-start;
gap: 8px;
margin-top: 6px;
font-size: 12px;
line-height: 18px;
}
.site-card-label{
flex: 0 0 84px;
font-size: 12px;
color: #909399;
white-space: nowrap;
}
.site-card-value{
flex: 1;
color: #606266;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.site-card-value--address{
white-space: normal;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: unset;
min-height: 36px;
}
&:hover{
border-color: #c0d3ff;
}
&.active{
border-color: #2b74ff;
box-shadow: 0 0 0 1px rgba(43,116,255,.15) inset;
background: #f5f9ff;
.site-card-name{
color: #2b74ff;
}
.site-card-value{
color: #5f8ee3;
}
}
}
} }
.single-zd-info-container{ .map-view{
font-size: 12px; height: 70vh;
margin-top: 10px; min-height: 520px;
color:#666666;
} }
} }
} }

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="login"> <div class="login">
<img :src="loginBg" alt="" srcset="" class="login-bg" /> <transition :name="bgTransitionName">
<img :key="bgNum" :src="loginBg" alt="" srcset="" class="login-bg" />
</transition>
<el-form <el-form
ref="loginForm" ref="loginForm"
:model="loginForm" :model="loginForm"
@ -107,6 +109,8 @@ export default {
data() { data() {
return { return {
bgNum: 1, bgNum: 1,
bgTransitionName: "bg-slide",
bgTransitionNames: ["bg-slide", "bg-zoom", "bg-blur"],
codeUrl: "", codeUrl: "",
loginForm: { loginForm: {
username: "admin", username: "admin",
@ -148,7 +152,12 @@ export default {
this.updateInterval(this.updateBgNum, 5000); this.updateInterval(this.updateBgNum, 5000);
}, },
methods: { methods: {
randomBgTransitionName() {
const index = Math.floor(Math.random() * this.bgTransitionNames.length);
return this.bgTransitionNames[index];
},
updateBgNum() { updateBgNum() {
this.bgTransitionName = this.randomBgTransitionName();
if (this.bgNum >= 4) this.bgNum = 0; if (this.bgNum >= 4) this.bgNum = 0;
this.bgNum += 1; this.bgNum += 1;
}, },
@ -210,6 +219,10 @@ export default {
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss">
.login { .login {
position: relative;
overflow: hidden;
width: 100%;
min-height: 100vh;
padding-left: 180px; padding-left: 180px;
display: flex; display: flex;
justify-content: left; justify-content: left;
@ -224,6 +237,60 @@ export default {
display: block; display: block;
height: 100%; height: 100%;
width: 100%; width: 100%;
object-fit: cover;
object-position: center;
backface-visibility: hidden;
will-change: opacity, transform, filter;
}
.bg-fade-enter-active,
.bg-fade-leave-active {
transition: opacity 0.9s ease, transform 0.9s ease;
}
.bg-fade-enter {
opacity: 0;
transform: scale(1.04);
}
.bg-fade-leave-to {
opacity: 0;
transform: scale(0.98);
}
.bg-slide-enter-active,
.bg-slide-leave-active {
transition: opacity 0.85s ease, transform 0.85s ease;
}
.bg-slide-enter {
opacity: 0;
transform: translate3d(3.5%, 0, 0) scale(1.01);
}
.bg-slide-leave-to {
opacity: 0;
transform: translate3d(-3.5%, 0, 0) scale(0.99);
}
.bg-zoom-enter-active,
.bg-zoom-leave-active {
transition: opacity 1s ease, transform 1s ease;
}
.bg-zoom-enter {
opacity: 0;
transform: scale(1.08);
}
.bg-zoom-leave-to {
opacity: 0;
transform: scale(0.94);
}
.bg-blur-enter-active,
.bg-blur-leave-active {
transition: opacity 0.9s ease, filter 0.9s ease, transform 0.9s ease;
}
.bg-blur-enter {
opacity: 0;
filter: blur(8px);
transform: scale(1.02);
}
.bg-blur-leave-to {
opacity: 0;
filter: blur(6px);
transform: scale(0.98);
} }
.login-logo { .login-logo {
display: block; display: block;