You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
central-control/src/views/HandleControl/index.vue

990 lines
28 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="control-view">
<!-- 主要内容 -->
<div class="main-content">
<!-- 顶部状态栏 -->
<div class="header-status">
<div class="time-info">
<span>{{ currentTime }} {{ week }}</span>
</div>
<div class="page-title">
<h1>手动控制</h1>
</div>
<div class="system-status">
<!-- <span>系统已运行{{ systemRuntime }}</span> -->
</div>
</div>
<!-- 主体区域 -->
<div class="control-content">
<!-- 左侧电力参数区域 -->
<div class="power-section">
<!-- ABC三相电力参数 -->
<div class="power-params">
<div v-for="phase in powerPhases" :key="phase.name" class="phase-card">
<div class="phase-name"><span>{{ phase.name }}</span>相</div>
<div class="param-list">
<div class="param-item">
<span class="param-label">电压:</span>
<span class="param-value">{{ phase.voltage }}</span>
</div>
<div class="param-item">
<span class="param-label">电流:</span>
<span class="param-value">{{ phase.current }}</span>
</div>
<div class="param-item">
<span class="param-label">有功功率:</span>
<span class="param-value">{{ phase.power }}</span>
</div>
<div class="param-item">
<span class="param-label">功率因数:</span>
<span class="param-value">{{ phase.factor }}</span>
</div>
</div>
</div>
<!-- 电能参数 -->
<div class="energy-card">
<div class="energy-name"><span>电</span><span>能</span><span>参</span><span>数</span></div>
<div class="param-list">
<!-- <div class="param-item">
<span class="param-label">DC电压:</span>
<span class="param-value">{{ energyParams.dcVoltage }}</span>
</div> -->
<div class="param-item">
<span class="param-label">有功电能:</span>
<span class="param-value">{{ energyParams.activeEnergy }}</span>
</div>
<div class="param-item">
<span class="param-label">有功功率:</span>
<span class="param-value">{{ energyParams.activePower }}</span>
</div>
<div class="param-item">
<span class="param-label">功率因数:</span>
<span class="param-value">{{ energyParams.powerFactor }}</span>
</div>
</div>
</div>
</div>
<!-- 回路控制区域 -->
<div class="circuit-section">
<!-- 上排回路 -->
<div class="circuit-row">
<div v-for="(circuit, index) in info.loops.slice(0, 5)" :key="circuit.name" class="circuit-item">
<div class="circuit-status" :class="{ active: circuit.is_open }">
回路{{ index + 1 }}{{ circuit.is_open ? '打开' : '关闭' }}
</div>
<div class="circuit-icon" :class="{ active: circuit.is_open }" @click="toggleCircuit(circuit.name)">
<el-icon>
<Sunny />
</el-icon>
</div>
</div>
</div>
<!-- 下排回路 -->
<div class="circuit-row">
<div v-for="(circuit, index) in info.loops.slice(5, 10)" :key="circuit.name" class="circuit-item">
<div class="circuit-status" :class="{ active: circuit.is_open }">
回路{{ index + 6 }}{{ circuit.is_open ? '打开' : '关闭' }}
</div>
<div class="circuit-icon" :class="{ active: circuit.is_open }" @click="toggleCircuit(circuit.name)">
<el-icon>
<Sunny />
</el-icon>
</div>
</div>
</div>
</div>
<!-- 调光通道区域 -->
<div class="dimming-section">
<!-- 上排调光通道 -->
<div class="dimming-row">
<div v-for="(channel, index) in info.loops.slice(0, 5)" :key="channel.name" class="dimming-item">
<div class="channel-label">调光通道{{ index + 1 }}</div>
<div class="brightness-display" :class="{ active: channel.pwm > 0 }" @click="openBrightnessDialog(channel.name)">
{{ channel.pwm }}%
</div>
</div>
</div>
<!-- 下排调光通道 -->
<div class="dimming-row">
<div v-for="(channel, index) in info.loops.slice(5, 10)" :key="channel.name" class="dimming-item">
<div class="channel-label">调光通道{{ index + 6 }}</div>
<div class="brightness-display" :class="{ active: channel.pwm > 0 }" @click="openBrightnessDialog(channel.name)">
{{ channel.pwm }}%
</div>
</div>
</div>
</div>
</div>
<!-- 右侧控制面板 -->
<div class="control-panel">
<!-- 切换模式 -->
<div class="control-group">
<div class="control-title">
<el-icon>
<Setting />
</el-icon>
切换模式
</div>
<el-button class="mode-button active" @click="changeMode">{{ info.disable ? '手动' : '自动' }}</el-button>
</div>
<!-- 灯控设置 -->
<div class="control-group">
<div class="control-title">
<el-icon>
<Sunny />
</el-icon>
灯控设置
</div>
<el-button class="setting-button" @click="openLightSettings">全开</el-button>
<el-button style="margin-left: 0;" class="setting-button" @click="turnOffAll">全关</el-button>
</div>
<!-- 亮度调节 -->
<div class="control-group">
<div class="control-title">
<el-icon>
<Sunny />
</el-icon>亮度调节
</div>
<el-button class="brightness-button" @click="openGlobalBrightnessDialog">一键调光</el-button>
</div>
</div>
</div>
</div>
<!-- 底部信息 -->
<div class="bottom-info">
<div class="bottom_item">
<div class="time-item">
<span>日出时间:{{ sunriseTime }}</span>
</div>
<div class="time-item">
<span>日落时间:{{ sunsetTime }}</span>
</div>
</div>
<div style="color:#fff">
设备号:{{ commit_uid }}
</div>
<div class="bottom_item">
<!-- <el-button class="restart-button" @click="restartSystem">
<el-icon>
<RefreshRight />
</el-icon>
远程重启
</el-button> -->
<el-button class="back-button" @click="goBack">
<el-icon>
<HomeFilled />
</el-icon>
返回主界面
</el-button>
</div>
</div>
<!-- 亮度调节弹窗 -->
<el-dialog v-model="brightnessDialogVisible" title="亮度调节" width="400px" :modal="true" class="brightness-dialog">
<div class="brightness-content">
<div class="brightness-label">亮度值:{{ currentBrightness }}%</div>
<div class="brightness-slider-container">
<el-slider v-model="currentBrightness" :min="0" :max="100" :show-stops="true" class="brightness-slider" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="brightnessDialogVisible = false">取消</el-button>
<el-button type="primary" style="background-color: #25b15f; border-color: #25b15f;" @click="confirmBrightness">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import RPC from '@/utils/rpc'
import dayjs from 'dayjs'
const commit_uid = sessionStorage.getItem('central_control_comm_uid')
// const commit_uid = '868656077692382'
const router = useRouter()
const currentTime = ref('')
const systemRuntime = ref('38分钟')
const brightnessDialogVisible = ref(false)
const currentBrightness = ref(50)
const selectedChannelName = ref(null)
const week = ref('')
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
// 电力参数数据
const powerPhases = ref([
{
name: 'A',
voltage: '0V',
current: '0A',
power: '0kW',
factor: '0'
},
{
name: 'B',
voltage: '0V',
current: '0A',
power: '0kW',
factor: '0'
},
{
name: 'C',
voltage: '0V',
current: '0A',
power: '0kW',
factor: '0'
}
])
const energyParams = ref({
dcVoltage: '0.0V',
activeEnergy: '0kW·h',
activePower: '0kW',
powerFactor: '0'
})
const isInitData = ref(true)
// 回路控制数据
const circuits = ref([
{ id: 1, name: '回路1', status: '关闭' },
{ id: 2, name: '回路2', status: '关闭' },
{ id: 3, name: '回路3', status: '关闭' },
{ id: 4, name: '回路4', status: '关闭' },
{ id: 5, name: '回路5', status: '关闭' },
{ id: 6, name: '回路6', status: '关闭' },
{ id: 7, name: '回路7', status: '关闭' },
{ id: 8, name: '回路8', status: '关闭' },
{ id: 9, name: '回路9', status: '关闭' },
{ id: 10, name: '回路10', status: '关闭' }
])
// 调光通道数据
const dimmingChannels = ref([
{ id: 1, name: '调光通道1', brightness: 0 },
{ id: 2, name: '调光通道2', brightness: 0 },
{ id: 3, name: '调光通道3', brightness: 0 },
{ id: 4, name: '调光通道4', brightness: 0 },
{ id: 5, name: '调光通道5', brightness: 0 },
{ id: 6, name: '调光通道6', brightness: 0 },
{ id: 7, name: '调光通道7', brightness: 0 },
{ id: 8, name: '调光通道8', brightness: 0 },
{ id: 9, name: '调光通道9', brightness: 0 },
{ id: 10, name: '调光通道10', brightness: 0 }
])
const info = ref({
"disable": false,
"backup_byte1": 0,
"loops": [
{
"index": 0,
"name": "#1",
"is_open": false,
"pwm": 0
},
{
"index": 1,
"name": "#2",
"is_open": false,
"pwm": 0
},
{
"index": 2,
"name": "#3",
"is_open": false,
"pwm": 0
},
{
"index": 3,
"name": "#4",
"is_open": false,
"pwm": 0
},
{
"index": 4,
"name": "#5",
"is_open": false,
"pwm": 0
},
{
"index": 5,
"name": "#6",
"is_open": false,
"pwm": 0
},
{
"index": 6,
"name": "#7",
"is_open": false,
"pwm": 0
},
{
"index": 7,
"name": "#8",
"is_open": false,
"pwm": 0
},
{
"index": 8,
"name": "#9",
"is_open": false,
"pwm": 0
},
{
"index": 9,
"name": "#10",
"is_open": false,
"pwm": 0
}
],
"backup_byte4": 0
})
const isSync = ref(true)
const getInfo = () => {
isSync.value = true
RPC.post('/m9z/getDeviceManualInfo', { comm_uid: commit_uid }).then(res => {
info.value = res
setTimeout(() => {
isSync.value = false
}, 1000);
}).finally(() => {
isInitData.value = false
})
}
const oldInfo = ref('')
watch(info, () => {
if (isInitData.value) return
if (oldInfo.value === JSON.stringify(info.value)) return
RPC.post('/m9z/setDeviceManualInfo', { comm_uid: commit_uid, ...info.value }).finally(() => {
if (!isSync.value) {
isSync.value = true
getInfo()
}
})
oldInfo.value = JSON.stringify(info.value)
}, { deep: true })
const sunriseTime = ref('')
const sunsetTime = ref('')
RPC.post('/m9z/getDeviceSunRiseTime', { comm_uid: commit_uid }).then(res => {
sunriseTime.value = res.Sunrise ? dayjs(res.Sunrise).format('HH:mm:ss') : ''
sunsetTime.value = res.Sunset ? dayjs(res.Sunset).format('HH:mm:ss') : ''
}).finally(() => {
RPC.post('/m9z/getDeviceManualInfo', { comm_uid: commit_uid }).then(res => {
info.value = res
setTimeout(() => {
isSync.value = false
}, 1000);
}).finally(() => {
isInitData.value = false
RPC.post('/adl400/readPhaseData', { comm_uid: commit_uid }).then(res => {
powerPhases.value[0].voltage = res.aVoltage + 'V'
powerPhases.value[0].current = res.aElectricCurrent + 'A'
powerPhases.value[0].power = res.aPower + 'kW'
powerPhases.value[0].factor = res.aPowerFactor
powerPhases.value[1].voltage = res.bVoltage + 'V'
powerPhases.value[1].current = res.bElectricCurrent + 'A'
powerPhases.value[1].power = res.bPower + 'kW'
powerPhases.value[1].factor = res.bPowerFactor
powerPhases.value[2].voltage = res.cVoltage + 'V'
powerPhases.value[2].current = res.cElectricCurrent + 'A'
powerPhases.value[2].power = res.cPower + 'kW'
powerPhases.value[2].factor = res.cPowerFactor
energyParams.value.activeEnergy = res.activeEnergy + 'kW·h'
energyParams.value.activePower = res.activePower + "kW"
energyParams.value.powerFactor = res.powerFactor
})
})
})
// 更新时间
const updateTime = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
week.value = weekDays[now.getDay()]
currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 切换回路状态
const toggleCircuit = (circuitName) => {
if (!info.value.disable) return ElMessage.warning('请切换至手动模式')
const circuit = info.value.loops.find(c => c.name === circuitName)
if (circuit) {
circuit.is_open = !circuit.is_open
}
}
// 打开亮度调节弹窗
const openBrightnessDialog = (channelName) => {
if (!info.value.disable) return ElMessage.warning('请切换至手动模式')
selectedChannelName.value = channelName
const channel = info.value.loops.find(c => c.name === channelName)
if (channel) {
currentBrightness.value = channel.pwm
brightnessDialogVisible.value = true
}
}
// 确认亮度设置
const confirmBrightness = () => {
const channel = info.value.loops.find(c => c.name === selectedChannelName.value)
if (channel) {
channel.pwm = currentBrightness.value
// ElMessage.success(`${channel.name}亮度已设置为${currentBrightness.value}%`)
} else {
info.value.loops.forEach(channel => {
channel.pwm = currentBrightness.value
})
}
brightnessDialogVisible.value = false
}
// 打开全局亮度调节
const openGlobalBrightnessDialog = () => {
if (!info.value.disable) return ElMessage.warning('请切换至手动模式')
selectedChannelName.value = null
currentBrightness.value = 50
brightnessDialogVisible.value = true
}
// 全开灯光
const openLightSettings = () => {
if (!info.value.disable) return ElMessage.warning('请切换至手动模式')
info.value.loops.forEach(circuit => {
circuit.is_open = true
})
// dimmingChannels.value.forEach(channel => {
// channel.brightness = 100
// })
}
// 全关灯光
const turnOffAll = () => {
if (!info.value.disable) return ElMessage.warning('请切换至手动模式')
info.value.loops.forEach(circuit => {
circuit.is_open = false
})
// dimmingChannels.value.forEach(channel => {
// channel.brightness = 0
// })
// ElMessage.success('所有灯光已关闭')
}
// 返回主界面
const goBack = () => {
router.push('/')
}
// 重启系统
const restartSystem = () => {
ElMessageBox.confirm(
`此操作将远程重启,是否继续?`,
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
RPC.post('/m9z/setRestart', { comm_uid: commit_uid }).then(res => {
ElMessage.success('远程重启成功')
getInfo()
})
})
}
const changeMode = () => {
ElMessageBox.confirm(
`此操作将切换到${info.value.disable ? '自动' : '手动'}模式,是否继续?`,
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
info.value.disable = !info.value.disable
// ElMessage({
// type: 'success',
// message: `切换${info.value.disable ? '手动' : '自动'}成功`,
// })
})
}
let timeInterval = null
// 处理 session 恢复后的重新加载
const handleSessionRestored = () => {
console.log('Session 已恢复,重新加载数据')
getInfo()
}
onMounted(() => {
updateTime()
timeInterval = setInterval(updateTime, 1000)
// 监听 session 恢复事件
window.addEventListener('m9z:session-restored', handleSessionRestored)
})
onUnmounted(() => {
if (timeInterval) {
clearInterval(timeInterval)
}
// 移除事件监听
window.removeEventListener('m9z:session-restored', handleSessionRestored)
})
</script>
<style scoped lang="scss">
.control-view {
width: 100vw;
height: 100vh;
box-sizing: border-box;
position: relative;
overflow: auto;
background: center / cover url('@/assets/images/@2x.png') no-repeat;
background-color: #0A3933;
}
.main-content {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
padding: 10px 20px;
box-sizing: border-box;
}
.header-status {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
color: rgba(255, 255, 255, 0.9);
}
.time-info {
font-size: 18px;
font-weight: 300;
width: 270px;
}
.page-title h1 {
font-size: 30px;
font-weight: bold;
margin: 0;
color: #ffffff;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.system-status {
min-width: 200px;
font-size: 18px;
font-weight: 300;
width: 270px;
}
.control-content {
display: flex;
width: 100%;
gap: 30px;
box-sizing: border-box;
height: calc(100% - 120px);
}
.power-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
/* width: 500px; */
gap: 20px;
}
.power-params {
display: flex;
justify-content: space-between;
gap: 10px;
}
.phase-card,
.energy-card {
display: flex;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
width: 23%;
overflow: hidden;
/* padding: 15px; */
backdrop-filter: blur(10px);
}
.phase-name,
.energy-name {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 15px;
background-color: #24806B;
width: 30px;
height: 100%;
}
.param-list {
display: flex;
flex-direction: column;
padding: 10px;
flex: 1;
gap: 8px;
justify-content: space-between;
}
.param-item {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.param-label {
color: rgba(255, 255, 255, 0.7);
}
.param-value {
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
}
.circuit-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.circuit-row {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.circuit-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 18%;
}
.circuit-status {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 8px 12px;
color: rgba(255, 255, 255, 0.7);
text-align: center;
width: 100%;
box-sizing: border-box;
}
.circuit-status.active {
background: rgba(31, 223, 130, 0.2);
border-color: rgba(35, 219, 62, 0.764);
color: #25b15f;
}
.circuit-icon {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: rgba(255, 255, 255, 0.5);
}
.circuit-icon.active {
background: rgba(255, 193, 7, 0.2);
border-color: rgba(255, 193, 7, 0.6);
color: #ffc107;
box-shadow: 0 0 15px rgba(255, 193, 7, 0.3);
}
.circuit-icon:hover {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
}
.dimming-section {
display: flex;
flex-direction: column;
gap: 10px;
font-size: 12px;
}
.dimming-row {
display: flex;
justify-content: space-between;
}
.dimming-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
width: 18%;
}
.channel-label {
color: rgba(255, 255, 255, 0.8);
text-align: center;
}
.brightness-display {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 6px;
color: rgba(255, 255, 255, 0.7);
font-weight: bold;
text-align: center;
width: 100%;
box-sizing: border-box;
cursor: pointer;
transition: all 0.3s ease;
}
.brightness-display.active {
background: rgba(31, 223, 130, 0.2);
border-color: rgba(35, 219, 62, 0.764);
color: #25b15f;
box-shadow: 0 0 10px rgba(33, 150, 243, 0.3);
}
.brightness-display:hover {
background: rgba(255, 255, 255, 0.12);
transform: translateY(-2px);
}
.bottom-info {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #eee;
width: 100vw;
padding: 0 30px;
height: 50px;
position: fixed;
bottom: 0;
left: 0;
z-index: 999;
box-sizing: border-box;
.bottom_item {
display: flex;
align-items: center;
}
.time-item {
color: #fff;
font-size: 16px;
margin: 0 20px;
}
.back-button,
.restart-button {
width: 100%;
height: 38px;
border-radius: 19px;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
}
.back-button {
// background-color: #000;
color: #fff;
border: #000;
background: linear-gradient(180deg, rgba(223, 196, 137, 1) 0%, rgba(190, 151, 99, 1) 100%);
color: #fff;
}
}
.control-panel {
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 20px;
}
.control-group {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 10px;
backdrop-filter: blur(10px);
}
.control-title {
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
font-weight: 500;
text-align: center;
margin-bottom: 15px;
display: flex;
align-items: center;
.el-icon {
font-size: 20px;
margin-right: 10px;
}
}
.mode-button,
.setting-button,
.brightness-button {
width: 100%;
height: 45px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
margin-bottom: 10px;
}
.mode-button {
background: rgba(255, 255, 255, 0.9);
border: none;
color: #333;
}
.mode-button.active {
background: linear-gradient(180deg, rgba(51, 224, 188, 1) 0%, rgba(5, 149, 117, 1) 100%);
color: #fff;
}
.setting-button {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.setting-button.active {
background: #1976d2;
border-color: #1976d2;
color: #ffffff;
}
.brightness-button {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.setting-button:hover,
.brightness-button:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-2px);
}
/* */
:deep(.brightness-dialog) {
background: rgba(30, 30, 30, 0.95);
backdrop-filter: blur(20px);
}
:deep(.brightness-dialog .el-dialog__header) {
// background: rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
:deep(.brightness-dialog .el-dialog__title) {
color: #ffffff;
font-size: 18px;
font-weight: 500;
}
:deep(.brightness-dialog .el-dialog__body) {
// background: rgba(255, 255, 255, 0.05);
color: #ffffff;
}
.brightness-content {
padding: 20px 0;
}
.brightness-label {
text-align: center;
font-size: 18px;
font-weight: 500;
margin-bottom: 30px;
color: #ffffff;
}
.brightness-slider-container {
padding: 0 20px;
}
:deep(.brightness-slider .el-slider__bar) {
background: #25b15f;
}
:deep(.brightness-slider .el-slider__button) {
border-color: #25b15f;
}
:deep(.brightness-slider .el-slider__stop) {
background: rgba(255, 255, 255, 0.3);
}
.dialog-footer {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 20px;
}
</style>