修改前端

This commit is contained in:
jingna 2026-05-18 12:39:57 +08:00
parent b8974dce59
commit 58f1768035
51 changed files with 4062 additions and 54 deletions

View File

@ -1,54 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { usePlatformStore } from './stores/platform'
import RealtimeView from './views/RealtimeView.vue'
import StatusView from './views/StatusView.vue'
import DeviceConfigView from './views/DeviceConfigView.vue'
import ChannelConfigView from './views/ChannelConfigView.vue'
import AlarmSettingView from './views/AlarmSettingView.vue'
import AlarmHistoryView from './views/AlarmHistoryView.vue'
import ControlView from './views/ControlView.vue'
import SystemConfigView from './views/SystemConfigView.vue'
const store = usePlatformStore()
const activeTab = ref('realtime')
const tabs = [
{ key: 'realtime', label: '实时数据', component: RealtimeView },
{ key: 'status', label: '设备状态', component: StatusView },
{ key: 'device', label: '设备配置', component: DeviceConfigView },
{ key: 'channel', label: '通道配置', component: ChannelConfigView },
{ key: 'alarm-setting', label: '报警设置', component: AlarmSettingView },
{ key: 'alarms', label: '报警历史', component: AlarmHistoryView },
{ key: 'control', label: '控制指令', component: ControlView },
{ key: 'system', label: '系统设置', component: SystemConfigView },
]
const currentComponent = computed(() => tabs.find((item) => item.key === activeTab.value)?.component ?? RealtimeView)
onMounted(() => {
void store.bootstrap()
})
</script>
<template>
<main class="app-shell">
<header class="page-header">
<h1>电气量测控平台</h1>
<p>RK3568 + FastAPI + WebSocket + Vue 的前后端开发框架骨架</p>
</header>
<section class="tabs">
<button
v-for="tab in tabs"
:key="tab.key"
:class="{ active: tab.key === activeTab }"
@click="activeTab = tab.key"
>
{{ tab.label }}
</button>
</section>
<component :is="currentComponent" :store="store.state" :actions="store" />
</main>
<router-view />
</template>

View File

@ -1,7 +1,7 @@
import axios from 'axios'
export const http = axios.create({
baseURL: 'http://127.0.0.1:8000/api',
baseURL: '/api',
timeout: 5000,
headers: {
'Content-Type': 'application/json',

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -204 -85 )">
<path d="M 20 18.6396677102804 C 19.9814177078209 19.3737533513783 19.3550029866943 19.9688473469092 18.5822812592228 20 L 1.44285949335955 20 C 0.670137756283424 19.9688473598131 0.0437230201672776 19.3737533605027 0 18.6396677102804 L 0 1.36033228971964 C 0.0437230201672776 0.626246639497268 0.670137756283424 0.0311526401869173 1.44285949335955 0 L 18.5822812592228 0 C 19.3550029866943 0.0311526530907713 19.9814177078209 0.626246648621674 20 1.36033228971964 L 20 18.6396677102804 Z M 12.1326119035907 9.41416406542056 L 9.66278625184458 9.41316719626168 L 7.4476034431874 13.1564693691589 L 10.1535333743236 14.1201246028038 L 9.05486146089524 17.494911728972 L 12.6835218888342 13.5346209813084 L 10.1538831529759 12.3051298130841 L 12.1326119035907 9.41416406542056 Z M 17.8827130103296 7 L 2.14242771765862 7 C 2.14242771765862 7.00934579439253 2.14242771765862 7.00934579439253 2.14242771765862 7.00934579439253 C 1.75606684912057 7.00934579439253 1.4428594810625 7.30689279404771 1.4428594810625 7.67393561915888 C 1.4428594810625 8.04097844427005 1.75606684912057 8.33852544392524 2.14242771765862 8.33852544392524 L 17.8827130103296 8.33852544392524 C 18.2690738788676 8.33852544392524 18.5822812469257 8.04097844427005 18.5822812469257 7.67393561915888 C 18.5822812469257 7.30689279404768 18.2690738788676 7.00934579439253 17.8827130103296 7.00934579439253 C 17.8827130103296 7.00934579439253 17.8827130103296 7.00934579439253 17.8827130103296 7 Z M 14.7346559517954 2.68951193925233 C 14.3482950900488 2.68951193925233 14.0350877274963 2.9870589336772 14.0350877274963 3.35410175233645 L 14.0350877274963 5.01557633177569 C 14.0350877274963 5.38261915688689 14.3482950955544 5.68016615654206 14.7346559640924 5.68016615654206 C 15.1210168326305 5.68016615654206 15.4342242006886 5.38261915688689 15.4342242006886 5.01557633177569 L 15.4342242006886 3.35410177570094 C 15.4342242006886 2.9870589336772 15.1210168381361 2.68951193925233 14.7346559763895 2.68951193925233 Z M 10.1874624200688 2.68951193925233 C 9.80110155832227 2.68951193925233 9.4878941957698 2.9870589336772 9.4878941957698 3.35410175233645 L 9.4878941957698 5.01557633177569 C 9.4878941957698 5.38261915688689 9.80110156382787 5.68016615654206 10.1874624323659 5.68016615654206 C 10.573823300904 5.68016615654206 10.8870306689621 5.38261915688689 10.8870306689621 5.01557633177569 L 10.8870306689621 3.35410177570094 C 10.8870306689621 2.9870589336772 10.5738233064096 2.68951193925233 10.187462444663 2.68951193925233 Z M 5.64026888834235 2.68951193925233 C 5.25390802659572 2.68951193925233 4.94070066404331 2.9870589336772 4.94070066404331 3.35410175233645 L 4.94070066404331 5.01557633177569 C 4.94070066404331 5.38261915688689 5.25390803210138 5.68016615654206 5.64026890063943 5.68016615654206 C 6.02662976917753 5.68016615654206 6.33983713723561 5.38261915688689 6.33983713723561 5.01557633177569 L 6.33983713723561 3.35410177570094 C 6.33983713723561 2.9870589336772 6.0266297746831 2.68951193925233 5.64026891293656 2.68951193925233 Z " fill-rule="nonzero" fill="#0099ff" stroke="none" transform="matrix(1 0 0 1 204 85 )" />
</g>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="50px" height="50px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -1120 -360 )">
<path d="M 49.8366012867643 5.39215686274486 C 49.8366012867643 2.50326795343153 47.4967319852942 0.163398713235551 44.607843137255 1 L 5.39215686274497 1 C 2.50326795343108 0.163398713235551 0.163398713235665 2.50326795343153 0.163398713235665 5.39215686274486 L 0.163398713235665 14 L 49.8366012867643 14 L 49.8366012867643 5.39215686274486 Z M 49 18 L 1 18 L 1 44.607843137255 C 0.163398713235665 47.4967320465686 2.50326801470578 49.8366012867643 5.39215686274497 49 L 44.607843137255 49 C 47.4967320465689 49.8366012867643 49.8366012867643 47.4967319852942 49 44.607843137255 L 49 18 Z M 12.1503267769606 32.8431372549019 L 17.2352941176471 37.9281045955884 C 18.0065359681371 38.6993464460781 18.0065359681371 39.9411764705883 17.2352941176471 40.6993463848039 C 16.4640522671571 41.4705882352942 15.2222222426468 41.4705882352942 14.4640522671571 40.6993463848039 L 7.99346403186269 34.2287581495094 C 7.62304306723877 33.862831504984 7.41456073994789 33.3638233409525 7.41456073994789 32.8431372549019 C 7.41456073994789 32.3224511688514 7.62304306723877 31.8234430048199 7.99346403186269 31.4575163602944 L 14.4640522671571 24.986928125 C 14.8299789140145 24.6165071414319 15.3289870912354 24.4080248020717 15.8496731924017 24.4080248020717 C 16.3703592935681 24.4080248020717 16.8693674707896 24.6165071414319 17.2352941176471 24.986928125 C 18.0065359681371 25.7581699754903 18.0065359681371 27.0000000000002 17.2352941176471 27.7581699142155 L 12.1503267769606 32.8431372549019 Z M 27.169934620098 22.6078431372551 C 28.2287581495098 22.8431372549019 28.9084966911768 23.8758169730394 28.6862745098043 24.9346405024511 L 25.1568627450985 41.5620914828432 C 24.9346405024511 42.6209150122551 23.8888889093139 43.3006535539215 22.830065379902 43.0784313725491 C 21.7712418504902 42.856209129902 21.0915033088233 41.8104575367644 21.313725490196 40.7516340073533 L 24.8431372549015 24.1241830269607 C 25.0653594975489 23.0653594975487 26.1111110906862 22.3856209558824 27.169934620098 22.6078431372551 Z M 42.0065359681373 31.4575163602944 C 42.7777778186273 32.2287582107842 42.7777778186273 33.4575163602941 42.0065359681373 34.2287581495094 L 35.5359477328429 40.6993463848039 C 34.7647058823532 41.4705882352942 33.522875857843 41.4705882352942 32.7647058823529 40.6993463848039 C 32.3942849177286 40.3334197402785 32.1858025904376 39.834411576247 32.1858025904376 39.3137254901964 C 32.1858025904376 38.7930394041453 32.3942849177286 38.2940312401138 32.7647058823529 37.9281045955884 L 37.8496732230394 32.8431372549019 L 32.7647058823529 27.7581699142155 C 32.3942849177286 27.3922432696901 32.1858025904376 26.8932351056586 32.1858025904376 26.3725490196075 C 32.1858025904376 25.8518629335569 32.3942849177286 25.3528547695254 32.7647058823529 24.986928125 C 33.1306325292104 24.6165071414319 33.6296407064319 24.4080248020717 34.1503268075983 24.4080248020717 C 34.6710129087646 24.4080248020717 35.1700210859855 24.6165071414319 35.5359477328429 24.986928125 L 42.0065359681373 31.4575163602944 Z " fill-rule="nonzero" fill="#0099ff" stroke="none" transform="matrix(1 0 0 1 1120 360 )" />
</g>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="50px" height="48px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -680 -361 )">
<path d="M 1.98245041436462 27 L 48.0886578038674 27 C 49.0932727209945 27.0057821227101 49.9086122928177 27.8297042861297 50 28.8448940273335 L 50 46.010639232335 C 49.9086122928177 47.0263526904908 49.0937909135007 47.8497511369584 48.0886578038674 47 L 1.98245041436462 47 C 0.977317304731287 47.8497511369584 0.162495925414532 47.0263526904908 1 46.010639232335 L 1 28.8448940273335 C 0.162495925414532 27.829704216342 0.977835566298381 27.0057821227101 1.98245041436462 27 Z M 14.0458203554854 37.4277666298343 C 14.0458203554854 36.1010523503381 13.001899572824 35.0148004714154 11.6895677486189 34.9759677348066 C 11.6658918571127 34.9752671500598 11.6422070859785 34.9749168013958 11.6185210460931 34.9749168013958 C 10.2779606542408 34.9749168013958 9.19122173670075 36.0730950759626 9.19122173670075 37.4277666298343 C 9.19122173670075 38.7824381837059 10.2779606542408 39.8806164582728 11.6185210460931 39.8806164582728 C 11.6422070859785 39.8806164582728 11.6658918571127 39.8802661096087 11.6895677486189 39.8795655248618 C 13.001899572824 39.8407327882532 14.0458203554854 38.7544809093304 14.0458203554854 37.4277666298343 Z M 23.7508577173637 37.4277666298343 C 23.7508577173637 36.1010523503381 22.7069369347023 35.0148004714154 21.3946051104972 34.9759677348066 C 21.3709292189913 34.9752671500598 21.3472444478568 34.9749168013958 21.3235584079714 34.9749168013958 C 19.9829980161192 34.9749168013958 18.8962590985791 36.0730950759626 18.8962590985791 37.4277666298343 C 18.8962590985791 38.7824381837059 19.9829980161192 39.8806164582728 21.3235584079714 39.8806164582728 C 21.3472444478568 39.8806164582728 21.3709292189913 39.8802661096087 21.3946051104972 39.8795655248618 C 22.7069369347023 39.8407327882532 23.7508577173637 38.7544809093304 23.7508577173637 37.4277666298343 Z M 38.3815404696131 35.5897056586217 L 33.5290217541437 35.5897056586217 C 32.5238885360327 35.5897056586217 31.7090671567157 36.4131041050894 31.7090671567157 37.4288175632451 C 31.7090671567157 38.4445310214009 32.5238885360327 39.2679294678686 33.5290216456658 39.2679294678686 L 38.3815404696131 39.2679294678686 C 39.386673470769 39.2679294678686 40.2014948500857 38.4445310214009 40.2014948500857 37.4288175632451 C 40.2014948500857 36.4131041050894 39.386673470769 35.5897056586217 38.3815403611354 35.5897056586217 Z M 1.98245041436462 0 L 48.0886578038674 0 C 49.0932727209945 0.0328412678103973 49.9086122928177 0.856763431230036 50 1.87195317243385 L 50 19.0376983774353 C 49.9086122928177 20.0534118355911 49.0937909135007 20.8768102820587 48.0886578038674 21 L 1.98245041436462 21 C 0.977317304731287 20.8768102820587 0.162495925414532 20.0534118355911 1 19.0376983774353 L 1 1.87195317243385 C 0.162495925414532 0.85676336144229 0.977835566298381 0.0328412678103973 1.98245041436462 0 Z M 14.0458203554851 10.4548257749346 C 14.0458203554851 9.12811149543842 13.001899572824 8.04185961651569 11.6895677486189 8.00302687990694 C 11.6658918571127 8.00232629516012 11.6422070859785 8.00197594649609 11.6185210460931 8.00197594649609 C 10.2779606542408 8.00197594649609 9.19122173670053 9.10015422106289 9.19122173670053 10.4548257749346 C 9.19122173670053 11.8094973288062 10.2779606542408 12.9076756033731 11.6185210460931 12.9076756033731 C 11.6422070859785 12.9076756033731 11.6658918571127 12.9073252547091 11.6895677486189 12.9066246699622 C 13.001899572824 12.8677919333535 14.0458203554851 11.7815400544307 14.0458203554851 10.4548257749346 Z M 23.7508577173637 10.4548257749346 C 23.7508577173637 9.12811149543842 22.7069369347023 8.04185961651569 21.3946051104972 8.00302687990694 C 21.370929218991 8.00232629516012 21.3472444478568 8.00197594649609 21.3235584079714 8.00197594649609 C 19.9829980161192 8.00197594649609 18.8962590985791 9.10015422106289 18.8962590985791 10.4548257749346 C 18.8962590985791 11.8094973288062 19.9829980161192 12.9076756033731 21.3235584079714 12.9076756033731 C 21.3472444478568 12.9076756033731 21.370929218991 12.9073252547091 21.3946051104972 12.9066246699622 C 22.7069369347023 12.8677919333535 23.7508577173637 11.7815400544307 23.7508577173637 10.4548257749346 Z M 38.3815404696131 8.61466293690029 L 33.5290217541437 8.61466293690029 C 32.5238886254397 8.61466293690029 31.7090672306629 9.43806139899044 31.7090672306629 10.4537748764176 C 31.7090672306629 11.4694883538447 32.5238886254397 12.2928868159349 33.5290217541437 12.2928868159349 L 38.3815404696131 12.2928868159349 C 39.3866735983172 12.2928868159349 40.201494993094 11.4694883538447 40.201494993094 10.4537748764176 C 40.201494993094 9.43806139899044 39.3866735983172 8.61466293690029 38.3815404696131 8.61466293690029 Z " fill-rule="nonzero" fill="#0099ff" stroke="none" transform="matrix(1 0 0 1 680 361 )" />
</g>
</svg>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import HeaderView from './components/header.vue'
import SidebarView from './components/sidebar.vue'
</script>
<template>
<div class="app-layout">
<header class="app-layout__header">
<HeaderView />
</header>
<main class="app-layout__main">
<SidebarView />
<div class="app-layout__main-content">
<router-view />
</div>
</main>
</div>
</template>
<style scoped lang="scss">
.app-layout {
width: 100%;
height: 100vh;
.app-layout__header {
width: 100%;
}
.app-layout__main {
width: 100%;
height: calc(100vh - 60px);
background-color: #F2F4F9;
display: flex;
}
.app-layout__main-content {
width: calc(100% - 240px);
}
}
</style>

View File

@ -0,0 +1,147 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { fetchDeviceStatus } from '@/api/platform'
const time = ref('')
const date = ref('')
const day = ref('')
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const updateTime = () => {
const now = new Date()
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const seconds = now.getSeconds().toString().padStart(2, '0')
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const dayOfMonth = now.getDate().toString().padStart(2, '0')
time.value = `${hours}:${minutes}:${seconds}`
date.value = `${year}-${month}-${dayOfMonth}`
day.value = weekDays[now.getDay()]
}
let timer: ReturnType<typeof setInterval>
const centerItems = ref([
{
label: '装置自检状态',
status: '--',
code: 'self-check'
},
{
label: '网络1状态',
status: '--',
code: 'network1'
},
{
label: '网络2状态',
status: '--',
code: 'network2'
},
{
label: '串口1状态',
status: '--',
code: 'serial1'
},
{
label: '串口2状态',
status: '--',
code: 'serial2'
}
])
async function refreshStatus() {
try {
const status:any = await fetchDeviceStatus()
centerItems.value.forEach(item => {
item.status = status[item.code] || '正常'
})
} catch (error) {
}
}
onMounted(() => {
updateTime()
timer = setInterval(updateTime, 1000)
refreshStatus()
})
onUnmounted(() => {
clearInterval(timer)
})
</script>
<template>
<div class="header-container">
<div class="header-container-left">
<img src="@/assets/images/dashboard/logo.svg" alt="">
<div style="margin-left: 10px;">电气量测控平台</div>
</div>
<div class="header-container-center">
<div v-for="item in centerItems" :key="item.label">
<div>{{ item.label }}<span style="font-weight: 700;" :style="{ color: item.status === '正常' ? '#009933' : '#FF0000' }">{{ item.status }}</span></div>
</div>
</div>
<div class="header-container-right">
<div class="header-container-right-time">
<div class="header-container-right-time1">{{ time }}</div>
<div class="header-container-right-time2">{{ date }} {{ day }}</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.header-container {
width: 100%;
height: 60px;
background-color: #ffffff;
border-radius: 5px 5px 0px 0px;
color: rgb(31, 41, 55);
display: flex;
align-items: center;
padding: 0 30px;
gap: 20px;
box-shadow: 0px 2px 8px rgb(219, 225, 236);
position: relative;
z-index: 10;
}
.header-container-left {
display: flex;
align-items: center;
font-weight: 700;
font-style: normal;
font-size: 18px;
color: #1F2937;
width: 210px;
}
.header-container-center {
display: flex;
align-items: center;
width: calc(100% - 300px);
gap: 40px;
font-weight: 400;
color: #505050;
}
.header-container-right-time {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 90px;
}
.header-container-right-time1 {
font-weight: 700;
font-style: normal;
font-size: 22px;
color: #363636;
}
.header-container-right-time2 {
font-weight: 400;
font-style: normal;
font-size: 10px;
color: #787878;
}
</style>

View File

@ -0,0 +1,275 @@
<script setup lang="ts">
import { ref } from 'vue'
import icon1 from '@/assets/images/menuicon/1.png'
import icon1_1 from '@/assets/images/menuicon/1-1.png'
import icon2 from '@/assets/images/menuicon/2.png'
import icon2_1 from '@/assets/images/menuicon/2-1.png'
import icon3 from '@/assets/images/menuicon/3.png'
import icon3_1 from '@/assets/images/menuicon/3-1.png'
import icon4 from '@/assets/images/menuicon/4.png'
import icon4_1 from '@/assets/images/menuicon/4-1.png'
import icon5 from '@/assets/images/menuicon/5.png'
import icon5_1 from '@/assets/images/menuicon/5-1.png'
import icon6 from '@/assets/images/menuicon/6.png'
import icon6_1 from '@/assets/images/menuicon/6-1.png'
import icon7 from '@/assets/images/menuicon/7.png'
import icon7_1 from '@/assets/images/menuicon/7-1.png'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const menuList = ref([
{
index: '1',
title: '显示信息',
icon: icon1,
iconHover: icon1_1,
children: [
{
index: '1-1',
title: '模拟量',
path: '/analogQuantity',
},
{
index: '1-2',
title: '开关量',
path: '/switchQuantity',
},
{
index: '1-3',
title: 'AI采集',
path: '/aiQuantity',
},
],
},
{
index: '2',
title: '事件报告',
icon: icon2,
iconHover: icon2_1,
path:'/eventReport',
},
{
index: '3',
title: '通讯设置',
icon: icon3,
iconHover: icon3_1,
path:'/communicationSetting',
},
{
index: '4',
title: '装置设置',
icon: icon4,
iconHover: icon4_1,
children: [
{
index: '4-1',
title: 'AI通道设置',
path: '/aiChannelSetting',
},
{
index: '4-2',
title: 'AO通道设置',
path: '/aoChannelSetting',
},
{
index: '4-3',
title: '密码设置',
path: '/passwordSetting',
},
{
index: '4-4',
title: '对时设置',
path: '/timeSetting',
},
{
index: '4-5',
title: '灯光设置',
path: '/lightSetting',
},
],
},
// {
// index: '5',
// title: '',
// icon: icon5,
// iconHover: icon5_1,
// path:'/recordSetting',
// },
{
index: '6',
title: '定值设置',
icon: icon6,
iconHover: icon6_1,
children: [
{
index: '6-1',
title: '线路定值设置',
path: '/lineSetting',
},
{
index: '6-2',
title: 'AI报警设置',
path: '/aiAlarmSetting',
},
],
},
{
index: '7',
title: '装置信息',
icon: icon7,
iconHover: icon7_1,
path:'/deviceInfo',
},
])
const handleClick = (path: any) => {
if (path === route.path || path == '') {
return
}
router.push(path)
}
const isActive = (path: any) => {
return route.path === path
}
</script>
<template>
<div class="sidebar-container">
<el-menu unique-opened>
<template v-for="menu in menuList" :key="menu.index">
<el-sub-menu v-if="menu.children" :index="menu.index">
<template #title>
<div class="menu-icon-wrapper">
<img :src="menu.icon" class="menu-icon" />
<img :src="menu.iconHover" class="menu-icon-hover" />
</div>
<span>{{ menu.title }}</span>
</template>
<el-menu-item
class="child-item"
v-for="child in menu.children"
:key="child.index"
:index="child.index"
@click="handleClick(child.path)"
:class="{ active: isActive(child.path) }"
>
{{ child.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item
v-else
:index="menu.index"
@click="handleClick(menu.path)"
:class="{ active: isActive(menu.path) }"
>
<div class="menu-icon-wrapper">
<img :src="menu.icon" class="menu-icon" />
<img :src="menu.iconHover" class="menu-icon-hover" />
</div>
<span>{{ menu.title }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</template>
<style scoped lang="scss">
.sidebar-container {
width: 240px;
height: calc(100vh - 60px);
background-color: #ffffff;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgba(229, 231, 235, 1);
border-top: none;
border-left: none;
border-bottom: none;
box-shadow: 2px 0px 8px rgba(219, 225, 236, 1);
overflow: auto;
}
:deep(.el-menu) {
background: transparent;
border-right: 0px;
}
:deep(.el-sub-menu__icon-arrow) {
transform: rotate(-90deg) !important;
}
:deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
transform: rotate(0deg) !important;
}
.menu-icon-wrapper {
position: relative;
width: 15px;
height: 15px;
margin-right: 15px;
display: inline-block;
vertical-align: middle;
}
.menu-icon,
.menu-icon-hover {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
transition: opacity 0s ease;
}
.menu-icon-hover {
opacity: 0;
}
:deep(.el-menu-item:hover) .menu-icon-hover,
:deep(.el-sub-menu__title:hover) .menu-icon-hover {
opacity: 1;
}
:deep(.el-menu-item:hover) .menu-icon,
:deep(.el-sub-menu__title:hover) .menu-icon {
opacity: 0;
}
:deep(.el-menu-item){
color: #505050 !important;
}
:deep(.el-sub-menu__title){
color: #505050 !important;
}
:deep(.el-menu-item){
padding-left: 30px !important;
}
:deep(.el-sub-menu__title){
padding-left: 30px !important;
}
:deep(.child-item){
padding-left: 60px !important;
}
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
background-color: transparent !important;
color: #0099ff !important;
}
:deep(.el-menu-item.active) {
background-color: rgba(0, 153, 255, 0.047) !important;
color: #0099ff !important;
border-right: 3px solid #0099ff !important;
}
:deep(.el-menu-item.active) .menu-icon-hover {
opacity: 1;
}
:deep(.el-menu-item.active) .menu-icon {
opacity: 0;
}
</style>

View File

@ -1,5 +1,14 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './routes'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
import './style.css'
const app = createApp(App)
createApp(App).mount('#app')
app.use(router)
app.use(ElementPlus as any, {
locale: zhCn,
})
app.mount('#app')

View File

@ -0,0 +1,252 @@
import {
createRouter,
createWebHistory,
type RouteRecordRaw,
} from "vue-router"
/**
*
*/
declare module "vue-router" {
interface RouteMeta {
title?: string
requiresAuth?: boolean
hidden?: boolean
keepAlive?: boolean
icon?: string
}
}
/**
*
*/
// src/routes/index.ts
const routes: RouteRecordRaw[] = [
// {
// path: "/login",
// name: "Login",
// component: () => import("@/views/login/index.vue"),
// meta: {
// title: "登录",
// hidden: true,
// },
// },
{
path: "/dashboard",
name: "Dashboard",
component: () => import("@/views/dashboard/index.vue"),
meta: {
title: "系统总览",
requiresAuth: false,
icon: "dashboard",
keepAlive: true,
},
},
{
path: "/",
component: () => import("@/layout/AppLayout.vue"),
meta: {
requiresAuth: false,
},
children: [
{
path: "",
redirect: "/dashboard",
},
{
path: "analogQuantity",
name: "AnalogQuantity",
component: () => import("@/views/analogQuantity/index.vue"),
meta: {
title: "模拟量",
},
},
{
path: "switchQuantity",
name: "SwitchQuantity",
component: () => import("@/views/switchQuantity/index.vue"),
meta: {
title: "开关量",
},
},
{
path: "aiQuantity",
name: "AiQuantity",
component: () => import("@/views/aiQuantity/index.vue"),
meta: {
title: "AI采集",
},
},
{
path: "eventReport",
name: "EventReport",
component: () => import("@/views/eventReport/index.vue"),
meta: {
title: "事件报告",
},
},
{
path: "communicationSetting",
name: "CommunicationSetting",
component: () => import("@/views/communicationSetting/index.vue"),
meta: {
title: "通信设置",
},
},
{
path: "aiChannelSetting",
name: "AiChannelSetting",
component: () => import("@/views/aiChannelSetting/index.vue"),
meta: {
title: "AI通道设置",
},
},
{
path: "aoChannelSetting",
name: "AoChannelSetting",
component: () => import("@/views/aoChannelSetting/index.vue"),
meta: {
title: "AO通道设置",
},
},
{
path: "timeSetting",
name: "TimeSetting",
component: () => import("@/views/timeSetting/index.vue"),
meta: {
title: "对时设置",
},
},
{
path: "passwordSetting",
name: "PasswordSetting",
component: () => import("@/views/passwordSetting/index.vue"),
meta: {
title: "密码设置",
},
},
{
path: "lightSetting",
name: "LightSetting",
component: () => import("@/views/lightSetting/index.vue"),
meta: {
title: "灯光设置",
},
},
{
path: "recordSetting",
name: "RecordSetting",
component: () => import("@/views/recordSetting/index.vue"),
meta: {
title: "录波功能",
},
},
{
path: "lineSetting",
name: "LineSetting",
component: () => import("@/views/lineSetting/index.vue"),
meta: {
title: "线路定值设置",
},
},
{
path: "aiAlarmSetting",
name: "AiAlarmSetting",
component: () => import("@/views/aiAlarmSetting/index.vue"),
meta: {
title: "AI报警设置",
},
},
{
path: "deviceInfo",
name: "DeviceInfo",
component: () => import("@/views/deviceInfo/index.vue"),
meta: {
title: "装置信息",
},
},
],
},
// {
// path: "/404",
// name: "NotFoundPage",
// component: () => import("../views/404/index.vue"),
// meta: {
// title: "404",
// hidden: true,
// },
// },
{
path: "/:pathMatch(.*)*",
redirect: "/404",
},
]
/**
*
* createWebHistory() Vue Router 4 history
*/
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior() {
return {
top: 0,
}
},
})
/**
*
*/
const WHITE_LIST = ["/dashboard", "/404"]
/**
* token
* Pinia / cookies / auth
*/
function getToken() {
return localStorage.getItem("token")
}
/**
*
*/
router.beforeEach((to, _from, next) => {
const token = getToken()
// 设置页面标题
if (to.meta?.title) {
document.title = `${to.meta.title} - 电气量测控平台`
} else {
document.title = "电气量测控平台"
}
// 不需要登录的页面直接放行
if (WHITE_LIST.includes(to.path)) {
next()
return
}
// 需要登录但没 token
if (to.meta?.requiresAuth && !token) {
next({
path: "/login",
query: {
redirect: to.fullPath,
},
})
return
}
next()
})
/**
*
*/
router.afterEach(() => {
// 这里后面可以接入进度条、埋点、页面访问日志之类
})
export default router

View File

@ -0,0 +1,193 @@
<script setup lang="ts">
import { ref } from 'vue'
const channelList = ref([
{ no: 1, type: '4~20mA', line: '开出一', down: 1, up: 8,delay:8,isInput:'0' },
{ no: 2, type: '1~5V', line: '开出一', down: 1, up: 8,delay:8,isInput:'0' },
{ no: 3, type: '4~20mA', line: '开出一', down: 1, up: 8,delay:8,isInput:'0' },
{ no: 4, type: '1~5V', line: '开出一', down: 1, up: 8,delay:8,isInput:'0' },
{ no: 5, type: '4~20mA', line: '开出一', down: 1, up: 8,delay:8,isInput:'0' },
{ no: 6, type: '1~5V', line: '开出一', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 7, type: '4~20mA', line: '开出一', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 8, type: '1~5V', line: '开出一', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 9, type: '1~5V', line: '开出二', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 10, type: '4~20mA', line: '开出二', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 11, type: '4~20mA', line: '开出二', down: 1, up: 8,delay:8,isInput:'1' },
{ no: 12, type: '4~20mA', line: '开出二', down: 1, up: 8,delay:8,isInput:'1' },
])
const nodeOptions = ref(['开出一', '开出二'])
const handleSave = () => {
console.log('保存AI通道设置', channelList.value)
}
</script>
<template>
<div class="ai-alarm-container">
<div class="panel">
<div class="row header-row">
<div class="cell cell-no">通道号</div>
<div class="cell cell-type" style="color: #787878;">信号类型</div>
<div class="cell cell-line">下限值</div>
<div class="cell cell-category">上限值</div>
<div class="cell cell-low">延时s</div>
<div class="cell cell-high">输出节点</div>
<div class="cell cell-high">是否投入</div>
</div>
<div class="row" v-for="(item, idx) in channelList" :key="idx">
<div class="cell cell-no">{{ item.no }}</div>
<div class="cell cell-type" style="background: #ffffff;">{{ item.type }}</div>
<div class="cell cell-line" style="background: #ffffff;">
<el-input-number v-model="item.down" style="width: 100%;" :controls="false" />
</div>
<div class="cell cell-low" style="background: #ffffff;">
<el-input-number v-model="item.up" style="width: 100%;" :controls="false" />
</div>
<div class="cell cell-high" style="background: #ffffff;">
<el-input-number v-model="item.delay" style="width: 100%;" :controls="false" />
</div>
<div class="cell cell-line" style="background: #ffffff;">
<el-select v-model="item.line">
<el-option v-for="line in nodeOptions" :key="line" :value="line" :label="line"></el-option>
</el-select>
</div>
<div class="cell cell-category" style="background: #ffffff;">
<el-switch v-model="item.isInput" active-value="1" inactive-value="0" />
</div>
</div>
</div>
<div class="btn-wrap">
<button class="save-btn" @click="handleSave">保存</button>
</div>
</div>
</template>
<style scoped lang="scss">
.ai-alarm-container {
width: 100%;
height: 100%;
padding: 20px;
.panel {
background: #fff;
border-radius: 6px;
padding: 16px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
margin: 0 0 16px 0;
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
}
.row {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.header-row .cell {
background-color: #f9fafe;
font-weight: 500;
}
.cell {
border: 1px solid #e4e4e4;
border-radius: 4px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
height: 37px;
box-sizing: border-box;
background-color: #f9fafe;
color: #787878;
&+.cell {
margin-left: 8px;
}
}
.cell-no {
width: 100px;
background: #f9fafe;
}
.cell-type {
flex: 1;
background: transparent;
color: #363636;
}
.cell-line {
flex: 1;
}
.cell-category {
flex: 1;
}
.cell-low {
flex: 1;
}
.cell-high {
flex: 1;
}
select,
input {
width: 85%;
height: 32px;
border: none;
outline: none;
text-align: center;
background: transparent;
font-size: 14px;
}
.btn-wrap {
margin-top: 20px;
}
.save-btn {
background-color: #0099ff;
color: #fff;
border: none;
border-radius: 4px;
width: 150px;
height: 40px;
font-size: 14px;
cursor: pointer;
}
}
// +
:deep(.el-select__wrapper),
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
text-align: center !important;
}
:deep(.el-select__wrapper.is-hover),
:deep(.el-input__wrapper.is-hover),
:deep(.el-select__wrapper.is-focused),
:deep(.el-input__wrapper.is-focused) {
box-shadow: none !important;
border: none !important;
}
:deep(.el-input__inner) {
text-align: center !important;
}
</style>

View File

@ -0,0 +1,194 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const channelList = ref([
{ no: 1, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 2, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 3, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 4, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 5, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 6, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 7, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 8, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 9, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 10, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 11, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 12, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
])
const lineOptions = ref(['线路一', '线路二'])
const categoryOptions = ref(['UA', 'UB', 'UC'])
const handleSave = () => {
console.log('保存AI通道设置', channelList.value)
}
function init() {
}
onMounted(() => {
init()
})
</script>
<template>
<div class="ai-channel-container">
<div class="title">AI通道设置</div>
<div class="panel">
<div class="row header-row">
<div class="cell cell-no">通道号</div>
<div class="cell cell-type" style="color: #787878;">信号类型</div>
<div class="cell cell-line">所属线路</div>
<div class="cell cell-category">类别</div>
<div class="cell cell-low">低端值</div>
<div class="cell cell-high">高端值</div>
</div>
<div class="row" v-for="(item, idx) in channelList" :key="idx">
<div class="cell cell-no">{{ item.no }}</div>
<div class="cell cell-type" style="background: #ffffff;">{{ item.type }}</div>
<div class="cell cell-line" style="background: #ffffff;">
<el-select v-model="item.line">
<el-option v-for="line in lineOptions" :key="line" :value="line" :label="line"></el-option>
</el-select>
</div>
<div class="cell cell-category" style="background: #ffffff;">
<el-select v-model="item.category">
<el-option v-for="cat in categoryOptions" :key="cat" :value="cat" :label="cat"></el-option>
</el-select>
</div>
<div class="cell cell-low" style="background: #ffffff;">
<el-input-number v-model="item.low" style="width: 100%;" :controls="false" />
</div>
<div class="cell cell-high" style="background: #ffffff;">
<el-input-number v-model="item.high" style="width: 100%;" :controls="false" />
</div>
</div>
</div>
<div class="btn-wrap">
<button class="save-btn" @click="handleSave">保存</button>
</div>
</div>
</template>
<style scoped lang="scss">
.ai-channel-container {
width: 100%;
height: 100%;
padding: 20px;
.panel {
background: #fff;
border-radius: 6px;
padding: 16px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
margin: 0 0 16px 0;
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
}
.row {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.header-row .cell {
background-color: #f9fafe;
font-weight: 500;
}
.cell {
border: 1px solid #e4e4e4;
border-radius: 4px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
height: 37px;
box-sizing: border-box;
background-color: #f9fafe;
color: #787878;
& + .cell {
margin-left: 8px;
}
}
.cell-no {
width: 100px;
background: #f9fafe;
}
.cell-type {
flex: 1;
background: transparent;
color: #363636;
}
.cell-line {
flex: 1;
}
.cell-category {
flex: 1;
}
.cell-low {
flex: 1;
}
.cell-high {
flex: 1;
}
select,
input {
width: 85%;
height: 32px;
border: none;
outline: none;
text-align: center;
background: transparent;
font-size: 14px;
}
.btn-wrap {
margin-top: 20px;
}
.save-btn {
background-color: #0099ff;
color: #fff;
border: none;
border-radius: 4px;
width: 150px;
height: 40px;
font-size: 14px;
cursor: pointer;
}
}
// +
:deep(.el-select__wrapper),
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
text-align: center !important;
}
:deep(.el-select__wrapper.is-hover),
:deep(.el-input__wrapper.is-hover),
:deep(.el-select__wrapper.is-focused),
:deep(.el-input__wrapper.is-focused) {
box-shadow: none !important;
border: none !important;
}
:deep(.el-input__inner){
text-align: center !important;
}
</style>

View File

@ -0,0 +1,207 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { usePlatformStore } from '../../stores/platform'
const { state, bootstrap } = usePlatformStore()
const aiData = computed(() => {
if (!state.realtime?.ai_collect) {
return Array.from({ length: 12 }, (_, i) => ({
channel: i + 1,
rawValue: '--',
line: '线路一',
category: '--',
lowValue1: 0,
lowValue2: 0,
}))
}
return Array.from({ length: 12 }, (_, i) => {
const channel = i + 1
const key = `ai_${channel}`
const rawValue = state.realtime!.ai_collect[key]
let category = '--'
let rawDisplay = '--'
if (rawValue !== undefined) {
if (channel % 2 === 1) {
const voltage = (rawValue / 1000).toFixed(3)
category = `UA: ${voltage}V`
rawDisplay = `${rawValue}mV`
} else {
const freq = ((rawValue / 1000) * 50).toFixed(4)
category = `f: ${freq}Hz`
rawDisplay = `${rawValue}mA`
}
}
return {
channel,
rawValue: rawDisplay,
line: '线路一',
category,
lowValue1: 1,
lowValue2: 8,
}
})
})
const tableData = computed(() => {
return state.alarms.slice(0, 10).map((alarm, index) => ({
name: '线路一',
typeName: alarm.type,
content: alarm.content,
time: alarm.time,
ts: '0',
}))
})
onMounted(() => {
void bootstrap()
})
</script>
<template>
<div class="ai-container">
<div class="ai-container-top">
<div class="top-title">AI采集信息</div>
<div class="container-top-box">
<!-- 表格表头 -->
<div class="table-header">
<div class="header-cell" style="width: 90px;max-width: 90px;min-width: 90px;">通道号</div>
<div class="header-cell">原始值</div>
<div class="header-cell">所属路线</div>
<div class="header-cell">类别</div>
<div class="header-cell">低值</div>
<div class="header-cell">低值</div>
</div>
<!-- 表格内容 -->
<div class="table-body">
<div v-for="(item, index) in aiData" :key="index" class="table-row">
<div class="cell" style="width: 90px;max-width: 90px;min-width: 90px;color: #787878;">{{ item.channel }}</div>
<div class="cell">{{ item.rawValue }}</div>
<div class="cell">{{ item.line }}</div>
<div class="cell">{{ item.category }}</div>
<div class="cell">{{ item.lowValue1 }}</div>
<div class="cell">{{ item.lowValue2 }}</div>
</div>
</div>
</div>
</div>
<div class="ai-container-bottom">
<div class="top-title">报警信息</div>
<div class="container-bottom-box">
<div v-for="(item, index) in tableData" :key="index" class="container-bottom-box-line1">
<div style="width: 8%; text-align: center;">{{ index + 1 }}</div>
<div style="width: 23%; text-align: center;">{{ item.name }}</div>
<div style="width: 15%; text-align: center;">{{ item.typeName }}</div>
<div style="width: 25%; text-align: center;">{{ item.content }}</div>
<div style="width: 20%; text-align: center;">{{ item.time }}</div>
<div style="width: 10%; text-align: center;">{{ item.ts }}ms</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.ai-container {
width: 100%;
height: 100%;
padding: 20px;
.top-title {
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
margin-bottom: 15px;
}
.ai-container-top {
width: 100%;
margin-bottom: 25px;
.container-top-box {
width: 100%;
height: 460px;
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
display: flex;
flex-direction: column;
.table-header {
display: flex;
width: 100%;
margin-bottom: 5px;
gap: 5px;
.header-cell {
flex: 1;
height: 28px;
line-height: 28px;
background-color: #f8f9fc;
border: 1px solid #e8e8e8;
border-radius: 4px;
text-align: center;
font-size: 14px;
color: #787878;
}
}
.table-body {
display: flex;
flex-direction: column;
gap: 5px;
.table-row {
display: flex;
width: 100%;
gap: 5px;
.cell {
flex: 1;
height: 28px;
line-height: 28px;
border: 1px solid #e8e8e8;
border-radius: 4px;
text-align: center;
font-size: 14px;
color: #363636;
}
}
}
}
}
.ai-container-bottom {
width: 100%;
.container-bottom-box {
width: 100%;
height: calc(100vh - 660px);
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
.container-bottom-box-line1 {
display: flex;
align-items: center;
color: #787878;
height: 40px;
font-size: 14px;
color: #505050;
border-bottom: 1px solid #f2f2f2;
&:last-child {
border-bottom: none;
}
}
}
}
}
</style>

View File

@ -0,0 +1,349 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { usePlatformStore } from '../../stores/platform'
import type { ValueGroup } from '../../types/platform'
const { state, bootstrap } = usePlatformStore()
const lineList = ref([
{ name: '线路一', lineNo: 1 },
{ name: '线路二', lineNo: 2 },
{ name: '线路三', lineNo: 3 },
{ name: '线路四', lineNo: 4 },
])
const checkedLine = ref('线路一')
const valList = ref([
{ name: '一次值', key: 'pri_val' },
{ name: '二次值', key: 'sec_val' },
])
const checkedVal = ref('一次值')
const currentLineData = computed(() => {
console.log(state)
if (!state.realtime) return null
const line = lineList.value.find(l => l.name === checkedLine.value)
if (!line) return null
return state.realtime.line_list.find(l => l.line_no === line.lineNo)
})
const currentValues = computed<ValueGroup | null>(() => {
if (!currentLineData.value) return null
const valKey = valList.value.find(v => v.name === checkedVal.value)?.key
return currentLineData.value[valKey as keyof typeof currentLineData.value] as ValueGroup || null
})
const frequency = computed(() => {
if (!currentValues.value) return '50.000'
return currentValues.value.frq.toFixed(4)
})
const sqrt3 = Math.sqrt(3)
const infoList = computed(() => {
const values = currentValues.value
if (!values) {
return [
{ name: '单项电压V', a: '--', b: '--', c: '--', total: '--' },
{ name: '单相电流A', a: '--', b: '--', c: '--', total: '--' },
{ name: '有功功率kw', a: '--', b: '--', c: '--', total: '--' },
{ name: '无功功率kvar', a: '--', b: '--', c: '--', total: '--' },
{ name: '视在功率kVA', a: '--', b: '--', c: '--', total: '--' },
{ name: '功率因数cos', a: '--', b: '--', c: '--', total: '--' },
]
}
return [
{
name: '单项电压V',
a: values.Ua.toFixed(3),
b: values.Ub.toFixed(3),
c: values.Uc.toFixed(3),
total: ((values.Ua + values.Ub + values.Uc) / 3).toFixed(3),
},
{
name: '单相电流A',
a: values.Ia.toFixed(3),
b: values.Ib.toFixed(3),
c: values.Ic.toFixed(3),
total: ((values.Ia + values.Ib + values.Ic) / 3).toFixed(3),
},
{
name: '有功功率kw',
a: ((values.Ua * values.Ia * 0.9) / 1000).toFixed(3),
b: ((values.Ub * values.Ib * 0.9) / 1000).toFixed(3),
c: ((values.Uc * values.Ic * 0.9) / 1000).toFixed(3),
total: (values.Pt / 1000).toFixed(3),
},
{
name: '无功功率kvar',
a: ((values.Ua * values.Ia * Math.sin(Math.acos(0.9))) / 1000).toFixed(3),
b: ((values.Ub * values.Ib * Math.sin(Math.acos(0.9))) / 1000).toFixed(3),
c: ((values.Uc * values.Ic * Math.sin(Math.acos(0.9))) / 1000).toFixed(3),
total: ((sqrt3 * values.Ua * values.Ia * Math.sin(Math.acos(0.9))) / 1000).toFixed(3),
},
{
name: '视在功率kVA',
a: ((values.Ua * values.Ia) / 1000).toFixed(3),
b: ((values.Ub * values.Ib) / 1000).toFixed(3),
c: ((values.Uc * values.Ic) / 1000).toFixed(3),
total: ((sqrt3 * values.Ua * values.Ia) / 1000).toFixed(3),
},
{
name: '功率因数cos',
a: '0.900',
b: '0.900',
c: '0.900',
total: '0.900',
},
]
})
const infoData = computed(() => {
const values = currentValues.value
if (!values) {
return { name: '线电压V', a: '--', b: '--', c: '--' }
}
return {
name: '线电压V',
a: (values.Ua * sqrt3).toFixed(3),
b: (values.Ub * sqrt3).toFixed(3),
c: (values.Uc * sqrt3).toFixed(3),
}
})
const tableData = computed(() => {
return state.alarms.slice(0, 10).map((alarm, index) => ({
name: `线路${currentLineData.value?.line_no || 1}`,
typeName: alarm.type,
content: alarm.content,
time: alarm.time,
ts: '0',
}))
})
function handleClick(name: string) {
checkedLine.value = name
}
function handleClicks(name: string) {
checkedVal.value = name
}
onMounted(() => {
checkedLine.value = lineList.value[0].name
checkedVal.value = valList.value[0].name
bootstrap()
})
</script>
<template>
<div class="analog-quantity-container">
<div class="analog-quantity-container-top">
<div class="top-title">实时信息</div>
<div class="container-top-box">
<div class="container-top-box-nav">
<div class="nav-box">
<div class="nav-box-line">
<div v-for="item in lineList" :key="item.name"
:class="{ 'nav-box-line-item-checked': item.name === checkedLine }" class="nav-box-line-item"
@click="handleClick(item.name)">
{{ item.name }}
</div>
</div>
<div class="nav-box-line">
<div v-for="item in valList" :key="item.name" style="padding: 0px 13px;"
:class="{ 'nav-box-line-item-checked': item.name === checkedVal }" class="nav-box-line-item"
@click="handleClicks(item.name)">
{{ item.name }}
</div>
</div>
</div>
<div class="nav-right">
<span>频率<span class="nav-right-value">{{ frequency }} HZ</span></span>
</div>
</div>
<div class="container-top-box-content">
<div class="container-top-box-content-top">
<div class="container-top-box-content-line1">
<div class="table-line1-item" style="border: none;background-color: transparent;">
<img src="@/assets/images/menuicon/noitem.png" style="width: 100%;height: 100%;" alt="">
</div>
<div class="table-line1-item">A相</div>
<div class="table-line1-item">B相</div>
<div class="table-line1-item">C相</div>
<div class="table-line1-item">三相总</div>
</div>
<div v-for="item in infoList" :key="item.name" class="container-top-box-content-line1">
<div class="table-line1-item" style="background-color: #f9fafe;">{{ item.name }}</div>
<div class="table-line1-item2">{{ item.a }}</div>
<div class="table-line1-item2">{{ item.b }}</div>
<div class="table-line1-item2">{{ item.c }}</div>
<div class="table-line1-item2">{{ item.total }}</div>
</div>
<div class="container-top-box-content-line1">
<div class="table-line1-item" style="width:calc(20% - 4px);">线电压V</div>
<div style="width: 80%;display: flex; gap: 5px;">
<div class="table-line1-item">Uab</div>
<div class="table-line1-item2">{{ infoData.a }}</div>
<div class="table-line1-item">Ubc</div>
<div class="table-line1-item2">{{ infoData.b }}</div>
<div class="table-line1-item">Uca</div>
<div class="table-line1-item2">{{ infoData.c }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="analog-quantity-container-bottom">
<div class="top-title">报警信息</div>
<div class="container-bottom-box">
<div v-for="(item, index) in tableData" :key="item.name" class="container-bottom-box-line1">
<div style="width: 8%;text-align: center;">{{ index + 1 }}</div>
<div style="width: 23%;text-align: center;">{{ item.name }}</div>
<div style="width: 15%;text-align: center;">{{ item.typeName }}</div>
<div style="width: 25%;text-align: center;">{{ item.content }}</div>
<div style="width: 20%;text-align: center;">{{ item.time }}</div>
<div style="width: 10%;text-align: center;">{{ item.ts }}ms</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.analog-quantity-container {
width: 100%;
height: 100%;
padding: 20px;
.top-title {
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
margin-bottom: 15px;
}
.analog-quantity-container-top {
width: 100%;
margin-bottom: 25px;
.container-top-box {
width: 100%;
height: 458px;
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
.container-top-box-nav {
display: flex;
justify-content: space-between;
align-items: center;
.nav-box {
display: flex;
gap: 20px;
.nav-box-line {
display: flex;
align-items: center;
height: 40px;
border: 1px solid rgba(228, 228, 228, 1);
border-radius: 6px;
padding: 4px;
}
.nav-box-line-item {
height: 32px;
line-height: 32px;
background-color: transparent;
cursor: pointer;
font-size: 14px;
color: #505050;
border-radius: 4px;
padding: 0px 24px;
text-align: center;
}
.nav-box-line-item-checked {
background-color: rgba(0, 153, 255, 1);
color: #FFFFFF;
}
}
.nav-right {
font-weight: 400;
color: #363636;
font-size: 16px;
.nav-right-value {
font-weight: 700;
color: #FF9900;
}
}
}
.container-top-box-content {
margin-top: 20px;
.container-top-box-content-top {
.container-top-box-content-line1 {
display: flex;
align-items: center;
margin-top: 5px;
gap: 5px;
color: #787878;
.table-line1-item {
width: 20%;
background: #f9fafe;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid #e4e4e4;
border-radius: 4px;
}
.table-line1-item2 {
width: 20%;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid #e4e4e4;
border-radius: 4px;
background: #ffffff;
color: #363636;
}
}
}
}
}
}
.analog-quantity-container-bottom {
width: 100%;
.container-bottom-box {
width: 100%;
height: calc(100vh - 660px);
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
overflow: auto;
.container-bottom-box-line1 {
display: flex;
align-items: center;
color: #787878;
height: 40px;
font-size: 14px;
color: #505050;
border-bottom: 1px solid #f2f2f2;
&:last-child {
border-bottom: none;
}
}
}
}
}
</style>

View File

@ -0,0 +1,194 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const channelList = ref([
{ no: 1, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 2, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 3, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 4, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 5, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 6, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 7, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 8, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 9, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 10, type: '1~5V', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 11, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
{ no: 12, type: '4~20mA', line: '线路一', category: 'UA', low: 1, high: 8 },
])
const lineOptions = ref(['线路一', '线路二'])
const categoryOptions = ref(['UA', 'UB', 'UC'])
const handleSave = () => {
console.log('保存ao通道设置', channelList.value)
}
function init() {
}
onMounted(() => {
init()
})
</script>
<template>
<div class="ao-channel-contaoner">
<div class="title">AO通道设置</div>
<div class="panel">
<div class="row header-row">
<div class="cell cell-no">通道号</div>
<div class="cell cell-type" style="color: #787878;">信号类型</div>
<div class="cell cell-line">所属线路</div>
<div class="cell cell-category">类别</div>
<div class="cell cell-low">低端值</div>
<div class="cell cell-high">高端值</div>
</div>
<div class="row" v-for="(item, idx) in channelList" :key="idx">
<div class="cell cell-no">{{ item.no }}</div>
<div class="cell cell-type" style="background: #ffffff;">{{ item.type }}</div>
<div class="cell cell-line" style="background: #ffffff;">
<el-select v-model="item.line">
<el-option v-for="line in lineOptions" :key="line" :value="line" :label="line"></el-option>
</el-select>
</div>
<div class="cell cell-category" style="background: #ffffff;">
<el-select v-model="item.category">
<el-option v-for="cat in categoryOptions" :key="cat" :value="cat" :label="cat"></el-option>
</el-select>
</div>
<div class="cell cell-low" style="background: #ffffff;">
<el-input-number v-model="item.low" style="width: 100%;" :controls="false" />
</div>
<div class="cell cell-high" style="background: #ffffff;">
<el-input-number v-model="item.high" style="width: 100%;" :controls="false" />
</div>
</div>
</div>
<div class="btn-wrap">
<button class="save-btn" @click="handleSave">保存</button>
</div>
</div>
</template>
<style scoped lang="scss">
.ao-channel-contaoner {
width: 100%;
height: 100%;
padding: 20px;
.panel {
background: #fff;
border-radius: 6px;
padding: 16px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
margin: 0 0 16px 0;
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
}
.row {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.header-row .cell {
background-color: #f9fafe;
font-weight: 500;
}
.cell {
border: 1px solid #e4e4e4;
border-radius: 4px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
height: 37px;
box-sizing: border-box;
background-color: #f9fafe;
color: #787878;
& + .cell {
margin-left: 8px;
}
}
.cell-no {
width: 100px;
background: #f9fafe;
}
.cell-type {
flex: 1;
background: transparent;
color: #363636;
}
.cell-line {
flex: 1;
}
.cell-category {
flex: 1;
}
.cell-low {
flex: 1;
}
.cell-high {
flex: 1;
}
select,
input {
width: 85%;
height: 32px;
border: none;
outline: none;
text-align: center;
background: transparent;
font-size: 14px;
}
.btn-wrap {
margin-top: 20px;
}
.save-btn {
background-color: #0099ff;
color: #fff;
border: none;
border-radius: 4px;
width: 150px;
height: 40px;
font-size: 14px;
cursor: pointer;
}
}
// +
:deep(.el-select__wrapper),
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
text-align: center !important;
}
:deep(.el-select__wrapper.is-hover),
:deep(.el-input__wrapper.is-hover),
:deep(.el-select__wrapper.is-focused),
:deep(.el-input__wrapper.is-focused) {
box-shadow: none !important;
border: none !important;
}
:deep(.el-input__inner){
text-align: center !important;
}
</style>

View File

@ -0,0 +1,273 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
//
const formData = ref({
networkCard: '网卡二',
ipAddress: '192.168.1.56',
subnetMask: '255.255.255.255',
gateway: '192.168.1.56',
tcpProtocol: 'Modbus TCP',
//
comPort: 'COM3',
baudRate: 115200,
parity: 'NONE',
dataBits: 8,
stopBits: 1,
rtuProtocol: 'Modbus RTU'
})
const networkCardOptions = [
{ label: '网卡一', value: '网卡一' },
{ label: '网卡二', value: '网卡二' }
]
const tcpProtocolOptions = [
{ label: 'Modbus TCP', value: 'Modbus TCP' },
{ label: 'IEC104 ', value: 'IEC104' }
]
const comPortOptions = [
{ label: 'COM1', value: 'COM1' },
{ label: 'COM2', value: 'COM2' },
{ label: 'COM3', value: 'COM3' }
]
const baudRateOptions = [
{ label: '9600', value: 9600 },
{ label: '19200', value: 19200 },
{ label: '38400', value: 38400 },
{ label: '57600', value: 57600 },
{ label: '115200', value: 115200 }
]
const parityOptions = [
{ label: 'NONE', value: 'NONE' },
{ label: 'EVEN', value: 'EVEN' },
{ label: 'ODD', value: 'ODD' }
]
const dataBitsOptions = [
{ label: '5', value: 5 },
{ label: '6', value: 6 },
{ label: '7', value: 7 },
{ label: '8', value: 8 }
]
const stopBitsOptions = [
{ label: '1', value: 1 },
{ label: '2', value: 2 }
]
const rtuProtocolOptions = [
{ label: 'Modbus RTU', value: 'Modbus RTU' },
{ label: 'IEC101 ', value: 'IEC101' }
]
//
const handleSave = () => {
ElMessageBox.prompt('请输入密码', '保存', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
// inputType: 'password',
})
.then(({ value }) => {
console.log(value)
// ElMessage({
// type: 'success',
// message: ``,
// })
})
.catch(() => {
// ElMessage({
// type: 'info',
// message: '',
// })
})
console.log('保存配置:', formData.value)
}
function init() {
}
onMounted(() => {
init()
})
</script>
<template>
<div class="communication-container">
<el-form :model="formData" label-width="100px" class="form-wrapper">
<div class="section">
<div class="section-title">
<div class="icon"></div>
<div>常规配置</div>
</div>
<div class="form-row">
<el-form-item label="网卡号">
<el-select v-model="formData.networkCard" placeholder="请选择">
<el-option v-for="item in networkCardOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="IP地址">
<el-input v-model="formData.ipAddress" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="子网掩码">
<el-input v-model="formData.subnetMask" />
</el-form-item>
<el-form-item label="默认网关">
<el-input v-model="formData.gateway" />
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="通讯协议">
<el-select v-model="formData.tcpProtocol" placeholder="请选择">
<el-option v-for="item in tcpProtocolOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item />
</div>
</div>
<div class="section">
<div class="section-title">
<div class="icon"></div>
<div>串口设置</div>
</div>
<div class="form-row">
<el-form-item label="串口号">
<el-select v-model="formData.comPort" placeholder="请选择">
<el-option v-for="item in comPortOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="波特率">
<el-select v-model="formData.baudRate" placeholder="请选择">
<el-option v-for="item in baudRateOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="校验位">
<el-select v-model="formData.parity" placeholder="请选择">
<el-option v-for="item in parityOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据位">
<el-select v-model="formData.dataBits" placeholder="请选择">
<el-option v-for="item in dataBitsOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="停止位">
<el-select v-model="formData.stopBits" placeholder="请选择">
<el-option v-for="item in stopBitsOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="通讯协议">
<el-select v-model="formData.rtuProtocol" placeholder="请选择">
<el-option v-for="item in rtuProtocolOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</div>
</div>
</el-form>
<div class="btn-wrapper">
<el-button type="primary" style="background-color: #0099ff;width: 150px;height: 40px;"
@click="handleSave">保存</el-button>
</div>
</div>
</template>
<style scoped lang="scss">
.communication-container {
width: 100%;
height: 100%;
padding: 20px;
.form-wrapper {
background: #fff;
padding: 20px 50px;
border-radius: 4px;
padding-bottom: 120px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.section {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.section-title {
font-size: 16px;
font-weight: 500;
margin: 0 0 21px 0;
display: flex;
align-items: center;
font-weight: 700;
font-style: normal;
font-size: 18px;
color: #333333;
.icon {
width: 4px;
height: 14px;
margin-right: 8px;
background-color: #0099ff;
}
}
}
.form-row {
display: flex;
gap: 40px;
margin-bottom: 16px;
.el-form-item {
flex: 1;
margin-bottom: 0;
}
}
.btn-wrapper {
margin-top: 20px;
}
}
:deep(.el-form-item--label-right .el-form-item__label) {
text-align: left;
justify-content: flex-start;
color: #787878;
}
:deep(.el-select__placeholder) {
color: #363636;
}
:deep(.el-input__inner) {
color: #363636;
min-height: 40px;
}
:deep(.el-select__wrapper) {
min-height: 40px;
}
</style>

View File

@ -0,0 +1,150 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import Sidebar from '@/layout/components/sidebar.vue'
const time = ref('')
const date = ref('')
const day = ref('')
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const updateTime = () => {
const now = new Date()
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const seconds = now.getSeconds().toString().padStart(2, '0')
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const dayOfMonth = now.getDate().toString().padStart(2, '0')
time.value = `${hours}:${minutes}:${seconds}`
date.value = `${year}-${month}-${dayOfMonth}`
day.value = weekDays[now.getDay()]
}
let timer: ReturnType<typeof setInterval>
onMounted(() => {
updateTime()
timer = setInterval(updateTime, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
</script>
<template>
<div class="dashboard-container">
<div class="dashboard-container-left">
<div class="dashboard-container-header">
<img src="@/assets/images/dashboard/logo.svg" alt="">
<div style="margin-left: 10px;">电气量测控平台</div>
</div>
<Sidebar />
</div>
<div class="dashboard-container-right">
<div class="dashboard-container-right-time">
<div class="dashboard-container-right-time1">{{ time }}</div>
<div class="dashboard-container-right-time2">{{ date }} {{ day }}</div>
</div>
<div class="dashboard-container-right-content">
<div class="dashboard-container-right-content-title">
<span>欢迎使用</span>
<span style="color: #0099FF;">电气量测控平台</span>
</div>
<div class="dashboard-container-right-content-title2">提供实时精准可靠的电力系统模拟量监测开关量监控及装置自检服</div>
<div class="dashboard-container-right-content-title2">助力电网安全运行</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.dashboard-container {
width: 100%;
height: 100vh;
display: flex;
}
.dashboard-container-left {
width: 240px;
height: 100vh;
}
.dashboard-container-header {
width: 100%;
height: 60px;
background-color: #ffffff;
border-width: 1px;
border-style: solid;
border-color: rgba(229, 231, 235, 1);
font-family: '思源黑体 Bold', '思源黑体 Regular', '思源黑体', sans-serif;
font-weight: 700;
font-style: normal;
font-size: 18px;
color: #1F2937;
border-top: none;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.dashboard-container-right {
width: calc(100% - 240px);
background: url('@/assets/images/dashboard/bg.png') no-repeat;
background-size: 100% 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.dashboard-container-right-content {
text-align: center;
}
.dashboard-container-right-content-title {
font-family: '思源黑体 Bold', '思源黑体 Regular', '思源黑体', sans-serif;
font-weight: 700;
font-style: normal;
font-size: 48px;
letter-spacing: normal;
color: #282828;
margin-bottom: 20px;
}
.dashboard-container-right-content-title2 {
font-family: '思源黑体', sans-serif;
font-weight: 400;
font-style: normal;
font-size: 20px;
color: #6B7280;
text-align: center;
}
.dashboard-container-right-time {
position: absolute;
top: 10px;
right: 30px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.dashboard-container-right-time1 {
font-weight: 700;
font-style: normal;
font-size: 22px;
color: #363636;
}
.dashboard-container-right-time2 {
font-weight: 400;
font-style: normal;
font-size: 10px;
color: #787878;
}
</style>

View File

@ -0,0 +1,116 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<div class="device-info-container">
<div class="container-box">
<div class="box-item">
<div class="box-item-content">
<div class="box-item-icon">
<img src="@/assets/images/menuicon/yj.svg" alt="">
</div>
<div class="box-item-text1">设备硬件信息</div>
<div class="box-item-line">
<div>板卡版本</div>
<div>B001.001.001</div>
</div>
<div class="box-item-line">
<div>显示版本</div>
<div>B001.001.001</div>
</div>
<div class="box-item-line">
<div>其他版本</div>
<div>B001.001.001</div>
</div>
</div>
</div>
<div class="box-item">
<div class="box-item-content">
<div class="box-item-icon">
<img src="@/assets/images/menuicon/bb.svg" alt="">
</div>
<div class="box-item-text1">程序版本信息</div>
<div class="box-item-line">
<div>显示程序版本</div>
<div>001.001.001</div>
</div>
<div class="box-item-line">
<div>通讯程序版本</div>
<div>001.001.001</div>
</div>
<div class="box-item-line">
<div>测量程序版本</div>
<div>001.001.001</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.device-info-container {
width: 100%;
height: 100%;
padding: 20px;
.container-box {
display: flex;
width: 100%;
height: 100%;
background: #ffffff;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
align-items: center;
justify-content: center;
gap: 100px;
min-height: 480px;
min-width: 820px;
}
.box-item {
width: 340px;
height: 440px;
border-radius: 10px;
background-color: rgba(0, 153, 255, 0.0470588235294118);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.box-item-content {
width: 230px;
text-align: center;
}
.box-item-icon {
width: 100px;
height: 100px;
background-color: rgba(0, 153, 255, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
}
.box-item-text1 {
font-size: 24px;
font-weight: 500;
color: #333333;
font-weight: 700;
font-size: 24px;
margin-top: 40px;
margin-bottom: 40px;
}
.box-item-line {
width: 230px;
font-size: 16px;
color: #505050;
line-height: 40px;
display: flex;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,192 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
const formData = reactive({
line: '线路一',
type: '操作事件',
timeRange: [],
})
const tableData = ref([
{
id: 1,
belong: '线路一',
type: '操作事件',
detail: '手动修改时间操作',
time: '2026-01-12 12:12:12 886ms',
},
{
id: 2,
belong: '线路一',
type: '操作事件',
detail: '手动修改时间操作',
time: '2026-01-12 12:12:12 886ms',
},
{
id: 3,
belong: '线路一',
type: '操作事件',
detail: '手动修改时间操作',
time: '2026-01-12 12:12:12 886ms',
},
{
id: 4,
belong: '线路一',
type: '操作事件',
detail: '手动修改时间操作',
time: '2026-01-12 12:12:12 886ms',
},
{
id: 5,
belong: '线路一',
type: '操作事件',
detail: '手动修改时间操作',
time: '2026-01-12 12:12:12 886ms',
},
])
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 36,
})
const handleQuery = () => {
console.log('查询', formData)
}
const handleSizeChange = (val: number) => {
pagination.pageSize = val
pagination.currentPage = 1
}
const handleCurrentChange = (val: number) => {
pagination.currentPage = val
}
function init() {
}
onMounted(() => {
formData.line = ''
formData.type = ''
formData.timeRange = []
init()
})
</script>
<template>
<div class="event-report-container">
<div class="search-box">
<el-select v-model="formData.line" placeholder="所属线路" style="width: 180px">
<el-option label="线路一" value="线路一" />
<el-option label="线路二" value="线路二" />
</el-select>
<el-select v-model="formData.type" placeholder="事件类型" style="width: 180px; margin-left: 15px">
<el-option label="操作事件" value="操作事件" />
<el-option label="报警事件" value="报警事件" />
</el-select>
<div>
<el-date-picker v-model="formData.timeRange" type="datetimerange" start-placeholder="开始时间"
end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
style="width: 380px; margin-left: 15px" />
</div>
<el-button type="primary" style="margin-left: 15px;" @click="handleQuery">
查询
</el-button>
</div>
<div class="table-box">
<el-table :data="tableData" style="width: 100%; height: calc(100vh - 280px)" header-align="center"
align="center">
<el-table-column label="序号" prop="id" width="80" />
<el-table-column label="事件所属" prop="belong" />
<el-table-column label="事件类型" prop="type" />
<el-table-column label="事件详情" prop="detail" />
<el-table-column label="发生时间" prop="time" />
</el-table>
<div class="pagination">
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 30, 40]" @size-change="handleSizeChange" @current-change="handleCurrentChange"
:total="pagination.total" layout="prev, pager, next, total, sizes" />
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.event-report-container {
width: 100%;
height: 100%;
padding: 20px;
}
.search-box {
background: #fff;
border-radius: 6px;
padding: 20px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
margin-bottom: 20px;
display: flex;
align-items: center;
height: 80px;
}
.table-box {
background: #fff;
border-radius: 4px;
padding: 20px;
box-shadow: 0 0 10px rgba(219, 225, 236, 1);
height: calc(100% - 100px);
}
.pagination {
margin-top: 15px;
display: flex;
justify-content: flex-start;
}
:deep(.el-table__header-wrapper) {
background-color: #f9fafe !important;
height: 46px;
line-height: 46px;
}
:deep(.el-table__header) {
th {
background-color: #f9fafe !important;
font-weight: 700 !important;
font-size: 14px !important;
color: #333 !important;
border: none !important;
}
}
:deep(.el-table) {
--el-table-row-hover-bg-color: #f8f9fa !important;
--el-table-border-color: #f2f2f2 !important;
}
:deep(.el-table td) {
border-bottom: 1px solid #f2f2f2 !important;
}
:deep(.el-table__cell) {
color: #505050 !important;
}
:deep(.el-input__inner) {
color: #363636;
min-height: 40px;
}
:deep(el-input__wrapper) {
min-height: 40px;
}
:deep(.el-select__wrapper) {
min-height: 40px;
}
:deep(.el-range-editor.el-input__wrapper) {
min-height: 40px;
}
</style>

View File

@ -0,0 +1,161 @@
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
//
const form = reactive({
sleepTime: 180, //
brightness: 80 //
})
//
const handleSave = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
console.log('灯光设置数据:', form)
//
alert('灯光设置保存成功!')
}
})
}
</script>
<template>
<div class="light-container">
<div class="light-container-content">
<div class="title">
<span class="title-line"></span>
灯光设置
</div>
<el-form ref="formRef" :model="form" label-width="100px" class="light-form">
<el-form-item label="息屏时间设置" prop="sleepTime">
<el-input v-model.number="form.sleepTime" type="number" min="0">
<template #suffix>
<span class="unit"></span>
</template>
</el-input>
</el-form-item>
<el-form-item label="屏幕亮度" prop="brightness">
<div class="slider-wrap">
<el-slider v-model="form.brightness" :min="0" :max="100" :step="1" style="width: 100%;" />
<span class="brightness-value">{{ form.brightness }}%</span>
</div>
</el-form-item>
</el-form>
</div>
<el-button type="primary" class="save-btn" @click="handleSave">
保存
</el-button>
</div>
</template>
<style scoped lang="scss">
.light-container {
width: 100%;
height: 100%;
padding: 20px;
.light-container-content {
width: 100%;
height: calc(100vh - 230px);
background-color: #ffffff;
padding: 20px 50px;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
font-size: 18px;
font-weight: 700;
color: #303133;
margin-bottom: 24px;
display: flex;
align-items: center;
.title-line {
display: inline-block;
width: 4px;
height: 14px;
background-color: #409eff;
margin-right: 8px;
font-weight: 700;
}
}
.light-form {
max-width: 600px;
margin-bottom: 24px;
.slider-wrap {
display: flex;
align-items: center;
gap: 16px;
width: 100%;
.brightness-value {
font-size: 16px;
color: #363636;
min-width: 40px;
}
}
.unit {
color: #909399;
}
}
.save-btn {
background-color: #0099ff;
width: 150px;
height: 40px;
font-size: 14px;
margin-top: 30px;
}
}
:deep(.el-form-item--label-right .el-form-item__label) {
text-align: left;
justify-content: flex-start;
color: #787878;
}
:deep(.el-input__inner) {
color: #363636;
min-height: 40px;
}
:deep(.el-slider__bar) {
background-color: #409eff;
}
:deep(.el-slider__button) {
border-color: #409eff;
}
:deep(.el-form-item__label) {
line-height: 40px !important;
}
:deep(.el-slider) {
height: 40px !important;
}
:deep(.el-slider__bar) {
height: 8px !important;
}
:deep(.el-slider__button) {
border-color: #ffffff;
border-width: 3px;
box-shadow: 0px 0px 5px rgb(0, 0, 0, 0.35);
background-color: #0099ff;
}
:deep(.el-slider__runway) {
height: 8px !important;
}
</style>

View File

@ -0,0 +1,298 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
const lineTabs = ref([
{
label: '线路一定值',
value: '1'
},
{
label: '线路二定值',
value: '2'
},
{
label: '线路三定值',
value: '3'
},
{
label: '线路四定值',
value: '4'
},
{
label: '线路五定值',
value: '5'
}
])
const activeTab = ref('1')
const infoList = ref({
category: {a: '180', b: '180', c: '开出1', d: '0'},
voltage: {a: '180', b: '180', c: '开出1', d: '0'},
current: {a: '180', b: '180', c: '开出1', d: '0'},
diffCurrent: {a: '180', b: '180', c: '开出1', d: '0'},
power: {a: '180', b: '180', c: '开出1', d: '0'},
frequency: {a: '180', b: '180', c: '开出1', d: '0'},
ptBreak: {a: '180', b: '180', c: '开出1', d: '1'},
ctBreak: {a: '180', b: '180', c: '开出1', d: '1'},
})
const handleSave = () => {
}
const options = ref([
{
label: '开出1',
value: '1'
},
{
label: '开出2',
value: '2'
},
])
</script>
<template>
<div class="line-page">
<div class="line-tabs">
<div class="tab-item" v-for="item in lineTabs" :key="item.value"
:class="{ 'is-active': activeTab === item.value }" @click="activeTab = item.value">
{{ item.label }}
</div>
</div>
<div class="line-tabs-box">
<div class="tab-content">
<div class="tab-content-item">
<div class="tab-content-item-box" style="height: 265px;">越限告警</div>
<div class="tab-content-item-box" style="height: 85px;">故障告警</div>
</div>
<div class="tab-content-item">
<div class="tab-content-item-box box-height">类别</div>
<div class="tab-content-item-box box-color box-height">电压</div>
<div class="tab-content-item-box box-color box-height">电流</div>
<div class="tab-content-item-box box-color box-height">差流</div>
<div class="tab-content-item-box box-color box-height">功率</div>
<div class="tab-content-item-box box-color box-height">频率</div>
<div class="tab-content-item-box box-color box-height">PT断线</div>
<div class="tab-content-item-box box-color box-height">CT断线</div>
</div>
<div class="tab-content-item">
<div class="tab-content-item-box box-height">限制</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.voltage.a" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.current.a" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.diffCurrent.a" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.power.a" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.frequency.a" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height" style="border: none;">
<img src="@/assets/images/menuicon/noitem.png" style="width: 100%; height: 100%;" alt="">
</div>
<div class="tab-content-item-box box-color box-height" style="border: none;">
<img src="@/assets/images/menuicon/noitem.png" style="width: 100%; height: 100%;" alt="">
</div>
</div>
<div class="tab-content-item">
<div class="tab-content-item-box box-height">延时s</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.voltage.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.current.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.diffCurrent.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.power.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.frequency.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.ptBreak.b" style="width: 100%;" :controls="false" />
</div>
<div class="tab-content-item-box box-color box-height">
<el-input-number v-model="infoList.ctBreak.b" style="width: 100%;" :controls="false" />
</div>
</div>
<div class="tab-content-item">
<div class="tab-content-item-box box-height">输出节点</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.voltage.c">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.current.c">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.diffCurrent.c">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.power.c">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.frequency.c">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.ptBreak.c" >
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div class="tab-content-item-box box-color box-height">
<el-select v-model="infoList.ctBreak.c" >
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
</div>
<div class="tab-content-item">
<div class="tab-content-item-box box-height">是否投入</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.voltage.d" active-value="1" inactive-value="0"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.current.d" active-value="1" inactive-value="0"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.diffCurrent.d" active-value="1" inactive-value="0"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.power.d" active-value="1" inactive-value="0"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.frequency.d" active-value="1" inactive-value="10"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.ptBreak.d" active-value="1" inactive-value="0"></el-switch>
</div>
<div class="tab-content-item-box box-color box-height">
<el-switch v-model="infoList.ctBreak.d" active-value="1" inactive-value="0"></el-switch>
</div>
</div>
</div>
<el-button type="primary" class="save-btn" @click="handleSave">保存</el-button>
</div>
</div>
</template>
<style scoped lang="scss">
.line-page {
width: 100%;
height: 100%;
padding: 20px;
.line-tabs {
display: flex;
gap: 10px;
padding-left: 20px;
.tab-item {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #e9ecf3;
border: 1px solid #dfe4ee;
border-radius: 10px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
box-shadow: 0px 0px 6px rgba(232, 236, 244, 1);
font-size: 14px;
color: #606060;
cursor: pointer;
}
.is-active {
color: #ffffff;
background-color: #0099ff;
border-color: #0099ff;
}
}
.line-tabs-box {
width: 100%;
.tab-content {
width: 100%;
display: flex;
padding: 60px 0;
background-color: #ffffff;
// height: calc(100vh - 260px);
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
padding: 20px 20px;
min-height: max-content;
gap: 5px;
.tab-content-item {
width: calc(100% / 6);
display: flex;
flex-direction: column;
gap: 5px;
.tab-content-item-box {
width: 100%;
background-color: #f9fafe;
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: rgba(228, 228, 228, 1);
border-radius: 4px;
font-size: 14px;
color: #787878;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
align-items: center;
}
.box-color {
background-color: #ffffff;
}
.box-height{
height: 40px;
}
}
}
.save-btn {
margin-top: 30px;
width: 150px;
height: 40px;
font-size: 14px;
background-color: #0099ff;
}
}
}
:deep(.el-select__wrapper),
:deep(.el-input__wrapper) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
text-align: center !important;
}
:deep(.el-select__wrapper.is-hover),
:deep(.el-input__wrapper.is-hover),
:deep(.el-select__wrapper.is-focused),
:deep(.el-input__wrapper.is-focused) {
box-shadow: none !important;
border: none !important;
}
:deep(.el-input__inner){
text-align: center !important;
}
</style>

View File

@ -0,0 +1,193 @@
<!-- <script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { login } from '@/api/auth'
import { ElMessage } from 'element-plus'
const route = useRoute()
const router = useRouter()
const loginForm = ref({
account: '',
password: ''
})
const loading = ref(false)
const error = ref('')
const loginFormRef = ref()
const loginRules = reactive({
account: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
]
})
const handleLogin = () => {
loginFormRef.value.validate((valid: any) => {
if (valid) {
loading.value = true;
const params = {
account: loginForm.value.account,
password: loginForm.value.password
};
login(params)
.then((response: any) => {
localStorage.setItem('token', response.token)
localStorage.setItem('username', response.user.username)
localStorage.setItem('account', response.user.account)
const redirect = (route.query.redirect as string) || '/dashboard'
router.push(redirect)
})
.catch((error: any) => {
ElMessage.error(error.message || '登录失败,请检查用户名和密码');
})
.finally(() => {
loading.value = false;
});
}
});
};
</script>
<template>
<div class="login-container">
<div class="login-title-box">
<div class="login-title">PEX50K台架试验数据监控系统</div>
</div>
<div class="login-form">
<div class="login-form-title">欢迎登录</div>
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login-form-box">
<el-form-item prop="account">
<el-input v-model="loginForm.account" placeholder="请输入用户名">
<template #prefix>
<img src="../../assets/login/yh.png" alt="" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码">
<template #prefix>
<img src="../../assets/login/mm.png" alt="" />
</template>
</el-input>
</el-form-item>
</el-form>
<div class="login-btn" @click="handleLogin">
<img src="../../assets/login/loginbt.png" alt="">
<span class="login-text">登录</span>
</div>
</div>
</div>
</template>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
background-repeat: no-repeat;
height: 100vh;
padding: 16px;
background-image: url('../../assets/login/dl_bg.png');
position: relative;
}
.login-title-box {
width: 100%;
height: 80px;
position: absolute;
top: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.login-title {
font-size: 50px;
font-weight: bold;
letter-spacing: 4px;
background: linear-gradient(to bottom, #FFFFFF 0%, #C3E3FF 75%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.login-form {
width: 650px;
height: 350px;
background-image: url('../../assets/login/dl_kuang.png');
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: right;
position: relative;
top: 80px;
right: -385px;
}
.login-form-title {
text-align: center;
font-size: 24px;
font-weight: 400;
color: #FFFFFF;
margin-top: 35px;
margin-bottom: 20px;
}
.login-form-box {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.login-form-box {
:deep(.el-input__wrapper) {
width: 300px;
height: 46px;
background-color: transparent;
border-radius: 4px;
/* border: 1px solid rgba(0, 153, 255, 0.5); */
box-shadow: 0 0 0 1px rgba(0, 153, 255, 0.5) inset;
position: relative;
top: 30px;
}
:deep(.el-input__inner) {
font-size: 16px;
font-weight: 400;
color: #FFFFFF;
}
:deep(.el-input__prefix-inner>:last-child) {
margin-right: 10px;
padding-left: 10px;
}
:deep(.el-form-item__error) {
padding-top: 32px;
}
}
.login-btn img {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
cursor: pointer;
position: relative;
right: -177px;
top: 20px;
}
.login-text {
position: absolute;
left: 305px;
top: 267px;
font-size: 20px;
color: #FFFFFF;
cursor: pointer;
}
</style> -->

View File

@ -0,0 +1,136 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const formRef = ref<FormInstance>()
//
const form = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
//
const rules: FormRules = {
oldPassword: [
{ required: true, message: '请输入原密码', trigger: 'blur' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度需在 6~20 位之间', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== form.newPassword) {
callback(new Error('两次输入的密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
]
}
//
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
console.log('表单数据:', form)
//
alert('密码修改成功!')
}
})
}
</script>
<template>
<div class="password-container">
<!-- 标题 -->
<div class="password-container-content">
<div class="title">
<span class="title-line"></span>
密码设置
</div>
<!-- 表单 -->
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" class="password-form">
<el-form-item label="原密码">
<el-input v-model="form.oldPassword" type="password" placeholder="请输入原密码" show-password />
</el-form-item>
<el-form-item label="新密码">
<el-input v-model="form.newPassword" type="password" placeholder="请输入新密码" show-password />
</el-form-item>
<el-form-item label="确认新密码">
<el-input v-model="form.confirmPassword" type="password" placeholder="请再次输入新密码" show-password />
</el-form-item>
</el-form>
</div>
<!-- 保存按钮 -->
<el-button type="primary" style="background-color: #0099ff;width: 150px;height: 40px;" class="save-btn" @click="handleSubmit">
保存
</el-button>
</div>
</template>
<style scoped lang="scss">
.password-container {
width: 100%;
height: 100%;
padding: 20px;
.password-container-content {
width: 100%;
height: calc(100vh - 230px);
background-color: #ffffff;
padding: 20px 50px;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
font-size: 18px;
font-weight: 700;
color: #303133;
margin-bottom: 24px;
display: flex;
align-items: center;
.title-line {
display: inline-block;
width: 4px;
height: 14px;
background-color: #409eff;
margin-right: 8px;
}
}
.password-form {
width: 60%;
}
.save-btn {
margin-top: 30px;
font-size: 14px;
}
}
:deep(.el-form-item--label-right .el-form-item__label) {
text-align: left;
justify-content: flex-start;
color: #787878;
}
:deep(.el-input__inner) {
color: #363636;
min-height: 40px;
}
:deep(.el-form-item__label) {
line-height: 40px !important;
}
</style>

View File

@ -0,0 +1,321 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
//
const activeTab = ref('manual')
//
const autoRecord = reactive({
overLimit: {
voltage: true,
current: false,
diffCurrent: true,
power: false,
frequency: true
},
fault: {
ptBreak: false,
ctBreak: true
},
switchIn: {
channel1: true,
channel2: false,
channel3: true,
channel4: false,
channel5: true,
channel6: false,
channel7: true,
channel8: false,
channel9: true,
channel10: false,
channel11: true,
channel12: false
}
})
const isRecording = ref(false)
//
const handleSave = () => {
alert('设置已保存')
}
//
const startRecord = () => {
isRecording.value = !isRecording.value
}
</script>
<template>
<div class="record-page">
<div class="record-tabs">
<div class="tab-item" :class="{ 'is-active': activeTab === 'manual' }" @click="activeTab = 'manual'">手动录波
</div>
<div class="tab-item" :class="{ 'is-active': activeTab === 'auto' }" @click="activeTab = 'auto'">自动录波</div>
</div>
<div class="record-tabs-box">
<div v-if="activeTab === 'manual'" class="manual-container">
<div class="circle-box">
<img src="@/assets/images/menuicon/lb.png" alt="record" />
</div>
<div class="text-box">
<div v-if="!isRecording">
<div class="text-box-text1">录波未开启</div>
<div class="text-box-text2">请点击启动录波进行录波</div>
</div>
<div v-else class="text-box-text3">
录波中...
</div>
</div>
<el-button type="primary" :disabled="isRecording" :class="{ 'disabled-btn': isRecording }"
class="record-btn" size="default" @click="startRecord">启动录波</el-button>
</div>
<div v-if="activeTab === 'auto'" class="auto-container">
<div class="auto-container-content">
<div class="auto-container-content-item">
<div class="box1" style="height: 403px;">越限录波</div>
<div class="box1" style="height: 133px;">故障录波</div>
</div>
<div class="auto-container-content-item">
<div class="box1 box-height">类别</div>
<div class="box1 box2 box-height">电压</div>
<div class="box1 box2 box-height">电流</div>
<div class="box1 box2 box-height">差流</div>
<div class="box1 box2 box-height">功率</div>
<div class="box1 box2 box-height">频率</div>
<div class="box1 box2 box-height">PT断线</div>
<div class="box1 box2 box-height">CT断线</div>
</div>
<div class="auto-container-content-item">
<div class="box1 box-height">是否投入</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.overLimit.voltage" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.overLimit.current" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.overLimit.diffCurrent" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.overLimit.power" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.overLimit.frequency" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.fault.ptBreak" />
</div>
<div class="box1 box2 box-height">
<el-switch v-model="autoRecord.fault.ctBreak" />
</div>
</div>
<div class="auto-container-content-item">
<div class="box1" style="height: 541px;">开入录波</div>
</div>
<div class="auto-container-content-item">
<div class="box1 box-height2">类别</div>
<div class="box1 box2 box-height2">通道1</div>
<div class="box1 box2 box-height2">通道2</div>
<div class="box1 box2 box-height2">通道3</div>
<div class="box1 box2 box-height2">通道4</div>
<div class="box1 box2 box-height2">通道5</div>
<div class="box1 box2 box-height2">通道6</div>
<div class="box1 box2 box-height2">通道7</div>
<div class="box1 box2 box-height2">通道8</div>
<div class="box1 box2 box-height2">通道9</div>
<div class="box1 box2 box-height2">通道10</div>
<div class="box1 box2 box-height2">通道11</div>
<div class="box1 box2 box-height2">通道12</div>
</div>
<div class="auto-container-content-item">
<div class="box1 box-height2">是否投入</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel1" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel2" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel3" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel4" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel5" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel6" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel7" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel8" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel9" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel10" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel11" />
</div>
<div class="box1 box2 box-height2">
<el-switch v-model="autoRecord.switchIn.channel12" />
</div>
</div>
</div>
<el-button type="primary" class="save-btn" @click="handleSave">保存</el-button>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.record-page {
width: 100%;
height: 100%;
padding: 20px;
.record-tabs {
display: flex;
gap: 10px;
padding-left: 20px;
.tab-item {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #e9ecf3;
border: 1px solid #dfe4ee;
border-radius: 10px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
box-shadow: 0px 0px 6px rgba(232, 236, 244, 1);
font-size: 14px;
color: #606060;
cursor: pointer;
}
.is-active {
color: #ffffff;
background-color: #0099ff;
border-color: #0099ff;
}
}
.record-tabs-box {
width: 100%;
}
}
.manual-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 60px 0;
background-color: #ffffff;
height: calc(100vh - 140px);
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
.circle-box {
position: relative;
width: 300px;
height: 300px;
margin-bottom: 20px;
}
.text-box {
text-align: center;
margin-bottom: 30px;
.text-box-text1 {
font-size: 24px;
margin: 0 0 6px;
color: #000000;
font-weight: 700;
}
.text-box-text2 {
font-size: 14px;
color: #949494;
margin: 0;
}
.text-box-text3 {
font-size: 24px;
color: #0099ff;
font-weight: 700;
margin: 20px 0px;
}
}
.record-btn {
width: 200px;
height: 50px;
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #FFFFFF;
background-color: #0099ff;
}
.disabled-btn {
background-color: #c0c4cc;
border-color: #c0c4cc;
}
}
.auto-container {
width: 100%;
.auto-container-content {
width: 100%;
// height: calc(100vh - 230px);
background-color: #ffffff;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
display: flex;
padding: 20px 20px;
gap: 5px;
min-height: min-content;
.auto-container-content-item {
width: 20%;
display: flex;
flex-direction: column;
gap: 5px;
}
.auto-container-content-item .box1 {
border: 1px solid #e4e4e4;
border-radius: 4px;
background-color: #f9fafe;
display: flex;
align-items: center;
justify-content: center;
color: #787878;
font-size: 14px;
}
.auto-container-content-item .box2 {
background-color: #ffffff;
}
.auto-container-content-item .box-height {
height: 63px;
min-height: 63px;
}
.auto-container-content-item .box-height2 {
height: 37px;
min-height: 37px;
}
}
.save-btn {
margin-top: 30px;
width: 150px;
height: 40px;
font-size: 14px;
background-color: #0099ff;
}
}
</style>

View File

@ -0,0 +1,210 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { usePlatformStore } from '../../stores/platform'
const { state, bootstrap } = usePlatformStore()
const inputStates = computed(() => {
if (!state.realtime?.switch) {
return Array(12).fill(false)
}
return Array.from({ length: 12 }, (_, i) => {
const key = `input_${i + 1}`
return state.realtime!.switch[key] === 1
})
})
const outputStates = computed(() => {
if (!state.realtime?.switch) {
return Array(12).fill(false)
}
return Array.from({ length: 12 }, (_, i) => {
const key = `output_${i + 1}`
return state.realtime!.switch[key] === 1
})
})
const tableData = computed(() => {
return state.alarms.slice(0, 10).map((alarm, index) => ({
name: '线路一',
typeName: alarm.type,
content: alarm.content,
time: alarm.time,
ts: '0',
}))
})
onMounted(() => {
void bootstrap()
})
</script>
<template>
<div class="switch-container">
<div class="switch-container-top">
<div class="top-title">开入/开出状态</div>
<div class="container-top-box">
<div class="state-column">
<div class="column-title">
<div class="column-title-icon"></div>
<div class="column-title-text">开入状态</div>
</div>
<div class="state-grid">
<div v-for="(state, index) in inputStates" :key="`input-${index}`" class="state-item">
<img v-if="state" src="@/assets/images/menuicon/off.png" style="width: 34px; height: 36px;"
alt="">
<img v-else src="@/assets/images/menuicon/on.png" alt="" style="width: 34px; height: 36px;">
<div class="label">开入{{ index + 1 }}</div>
</div>
</div>
</div>
<div class="state-column">
<div class="column-title">
<div class="column-title-icon"></div>
<div class="column-title-text">开出状态</div>
</div>
<div class="state-grid">
<div v-for="(state, index) in outputStates" :key="`output-${index}`" class="state-item">
<img v-if="state" src="@/assets/images/menuicon/off2.png" style="width: 46px; height: 24px;"
alt="">
<img v-else src="@/assets/images/menuicon/on2.png" style="width: 46px; height: 24px;"
alt="">
<div class="label">开出{{ index + 1 }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="switch-container-bottom">
<div class="top-title">报警信息</div>
<div class="container-bottom-box">
<div v-for="(item, index) in tableData" :key="index" class="container-bottom-box-line1">
<div style="width: 8%; text-align: center;">{{ index + 1 }}</div>
<div style="width: 23%; text-align: center;">{{ item.name }}</div>
<div style="width: 15%; text-align: center;">{{ item.typeName }}</div>
<div style="width: 25%; text-align: center;">{{ item.content }}</div>
<div style="width: 20%; text-align: center;">{{ item.time }}</div>
<div style="width: 10%; text-align: center;">{{ item.ts }}ms</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.switch-container {
width: 100%;
height: 100%;
padding: 20px;
.top-title {
font-weight: 700;
font-style: normal;
font-size: 16px;
color: #363636;
margin-bottom: 15px;
}
.switch-container-top {
width: 100%;
margin-bottom: 25px;
.container-top-box {
width: 100%;
height: 458px;
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
display: flex;
gap: 40px;
.state-column {
flex: 1;
display: flex;
flex-direction: column;
.column-title {
font-size: 18px;
font-weight: 700;
color: #363636;
margin-bottom: 20px;
display: flex;
align-items: center;
.column-title-icon {
width: 4px;
height: 14px;
background-color: #409EFF;
}
.column-title-text {
font-size: 18px;
font-weight: 700;
color: #363636;
margin-left: 10px;
}
}
.state-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
padding: 20px 10px;
background-color: rgba(249, 250, 254, 1);
height: 380px;
align-items: center;
justify-items: center;
gap: 0;
.state-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 65px;
.label {
font-size: 14px;
color: #363636;
height: 20px;
line-height: 20px;
//
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
}
.switch-container-bottom {
width: 100%;
.container-bottom-box {
width: 100%;
height: calc(100vh - 660px);
padding: 20px;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
border-radius: 4px;
.container-bottom-box-line1 {
display: flex;
align-items: center;
color: #787878;
height: 40px;
font-size: 14px;
color: #505050;
border-bottom: 1px solid #f2f2f2;
&:last-child {
border-bottom: none;
}
}
}
}
}
</style>

View File

@ -0,0 +1,107 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
const formRef = ref<FormInstance>()
const form = reactive({
targetTime: ''
})
const rules: FormRules = {
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
console.log('表单数据:', form)
}
})
}
</script>
<template>
<div class="time-container">
<!-- 标题 -->
<div class="time-container-content">
<div class="title">
<span class="title-line"></span>
对时设置
</div>
<!-- 表单 -->
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" class="time-form">
<el-form-item label="选择时间" prop="targetTime">
<el-date-picker v-model="form.targetTime" type="datetime" placeholder="请选择时间"
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%;" />
</el-form-item>
</el-form>
</div>
<el-button type="primary" style="background-color: #0099ff;width: 150px;height: 40px;" class="save-btn"
@click="handleSubmit">
更改
</el-button>
</div>
</template>
<style scoped lang="scss">
.time-container {
width: 100%;
height: 100%;
padding: 20px;
.time-container-content {
width: 100%;
height: calc(100vh - 230px);
background-color: #ffffff;
padding: 20px 50px;
border-radius: 4px;
box-shadow: 0px 0px 10px rgba(219, 225, 236, 1);
}
.title {
font-size: 18px;
font-weight: 700;
color: #303133;
margin-bottom: 24px;
display: flex;
align-items: center;
.title-line {
display: inline-block;
width: 4px;
height: 14px;
background-color: #409eff;
margin-right: 8px;
}
}
.time-form {
width: 60%;
}
.save-btn {
margin-top: 30px;
font-size: 14px;
}
}
:deep(.el-form-item--label-right .el-form-item__label) {
text-align: left;
justify-content: flex-start;
color: #787878;
}
:deep(.el-input__inner) {
color: #363636;
min-height: 40px;
}
:deep(.el-form-item__label) {
line-height: 40px !important;
}
:deep(.el-input__wrapper) {
min-height: 40px !important;
}
</style>

View File

@ -1,7 +1,9 @@
import type { AlarmEvent, RealtimeData } from '../types/platform'
function buildWsUrl(path: string): string {
return `ws://127.0.0.1:8000${path}`
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
const host = window.location.host
return `${protocol}://${host}${path}`
}
export function connectRealtime(onMessage: (data: RealtimeData) => void): WebSocket {

View File

@ -9,7 +9,11 @@
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ES2020", "DOM"],
"types": ["vite/client"]
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}

View File

@ -1,10 +1,27 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
server: {
host: '0.0.0.0',
host: 0.0.0.0,
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/ws': {
target: 'ws://localhost:8000',
changeOrigin: true,
ws: true,
},
},
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
})