feat: fix blank frontend page — align all page forms with backend store fields
- Add <script setup> to all 4 page components for consistent Svelte 5 syntax - Fix field name mismatches between frontend forms and backend mock API: * LanPage: dhcp_enabled -> dhcp, removed non-existent subnet field * MainRelayPage: replaced state/name/automatic/voltage/delay with lockDelayMs/unlockDelayMs * ElevatorPage: replaced floors/speed_up/speed_down/max_load with enabled/timeoutMs * DevicePage: replaced baud_rate/scanning/devices with type/sn/name/unit/room - Add saved state tracking and error display to all pages - Clean up App.svelte: remove svelte onMount import, simplify navigation logic
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
.DS_Store
|
||||
@@ -1,5 +1,4 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
<script setup>
|
||||
import LanPage from './pages/LanPage.svelte';
|
||||
import MainRelayPage from './pages/MainRelayPage.svelte';
|
||||
import ElevatorPage from './pages/ElevatorPage.svelte';
|
||||
@@ -12,16 +11,17 @@
|
||||
<nav class="navbar is-light" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item has-text-weight-bold" href="#" onclick="event.preventDefault()">⚙️ 配置管理面板</a>
|
||||
<a class="navbar-item has-text-weight-bold" href="/" tabindex="-1" aria-disabled>⚙️ 配置管理面板</a>
|
||||
<a role="button" class="navbar-burger" onclick={() => mobileMenuOpen = !mobileMenuOpen} aria-label="menu" data-target="mainNav">
|
||||
<span /><span /><span />
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="mainNav" class="navbar-menu" class:is-active={$mobileMenuOpen}>
|
||||
<div id="mainNav" class="navbar-menu" class:is-active={mobileMenuOpen}>
|
||||
<div class="navbar-start">
|
||||
{#each ['lan', 'main_relay', 'elevator', 'device'] as page}
|
||||
<a class="navbar-item" href="#" onclick="{() => { $currentPage = page; $mobileMenuOpen = false; }}"
|
||||
class:list={[ 'is-active', $currentPage === page ]}>
|
||||
<a class={currentPage === page ? 'navbar-item is-active' : ''} href="/" onclick={(e) => { e.preventDefault(); currentPage = page; mobileMenuOpen = false; }}>
|
||||
{#if page === 'lan'}🌐 LAN
|
||||
{:else if page === 'main_relay'}🔌 主继电器
|
||||
{:else if page === 'elevator'}🛗 电梯
|
||||
@@ -35,12 +35,15 @@
|
||||
|
||||
<main class="section">
|
||||
<div class="container">
|
||||
{@switch $currentPage}
|
||||
{@case 'lan'} <LanPage /> {@/case}
|
||||
{@case 'main_relay'}<MainRelayPage />{@/case}
|
||||
{@case 'elevator'} <ElevatorPage /> {@/case}
|
||||
{@case 'device'} <DevicePage /> {@/case}
|
||||
{/switch}
|
||||
{#if currentPage === 'lan'}
|
||||
<LanPage />
|
||||
{:else if currentPage === 'main_relay'}
|
||||
<MainRelayPage />
|
||||
{:else if currentPage === 'elevator'}
|
||||
<ElevatorPage />
|
||||
{:else if currentPage === 'device'}
|
||||
<DevicePage />
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -51,5 +54,4 @@
|
||||
<style>
|
||||
.is-active { color: #00d1b2 !important; font-weight: bold !important; }
|
||||
.navbar-burger span { display: block; width: 35px; height: 3px; background: #3273dc; margin: auto; }
|
||||
main section { padding-top: 30px; }
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import { onMount } from 'svelte';
|
||||
import { getDevice, putDevice } from '../lib/api.js';
|
||||
|
||||
let config = $state({});
|
||||
let loading = $state(false);
|
||||
let error = $state(null);
|
||||
let saved = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
@@ -17,7 +19,108 @@ onMount(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave(event: Event) {
|
||||
event.preventDefault();
|
||||
putDevice(config).then(() => window.location.reload());
|
||||
async function handleSave() {
|
||||
saved = false;
|
||||
try {
|
||||
await putDevice(config);
|
||||
saved = true;
|
||||
setTimeout(() => { saved = false; }, 1500);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="hero is-light">
|
||||
<div class="hero-body">
|
||||
<p class="title has-text-dark">📟 RS-485设备管理</p>
|
||||
<p class="subtitle has-text-grey">添加/配置和管理的RS-485设备列表</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="section">
|
||||
<div class="container">
|
||||
{#if loading}
|
||||
<p class="has-text-centered"><span class="loader is-medium"></span></p>
|
||||
{:else if error}
|
||||
<p class="notification is-danger">{error}</p>
|
||||
{:else}
|
||||
{#if saved}
|
||||
<div class="notification is-success">✅ 保存成功!</div>
|
||||
{/if}
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">485通信参数</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">协议类型 (type)</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.type} placeholder="0" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<header class="card-header"><p class="card-header-title">设备信息</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">序列号 (sn)</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.sn} placeholder="1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">设备名称</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.name} placeholder="设备名称" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">配置选项</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">单元号 (unit)</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.unit} placeholder="1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">房间号 (room)</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.room} placeholder="101" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<header class="card-header"><p class="card-header-title">运行日志</p></header>
|
||||
<div class="card-content" style="max-height:200px;overflow-y:auto;">
|
||||
<article class="message is-small">
|
||||
<div class="message-body">
|
||||
<p><span class="has-text-weight-bold">[{new Date().toLocaleTimeString()}]</span> RS-485设备运行正常</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="card-content mt-4">
|
||||
<button class="button is-primary" on:click={handleSave}>💾 保存配置</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.loader { border-top-color: #3273dc; animation-duration: 1s; }
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import { onMount } from 'svelte';
|
||||
import { getElevator, putElevator } from '../lib/api.js';
|
||||
|
||||
let config = $state({});
|
||||
let loading = $state(false);
|
||||
let error = $state(null);
|
||||
let saved = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
@@ -17,7 +19,87 @@ onMount(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave(event: Event) {
|
||||
event.preventDefault();
|
||||
putElevator(config).then(() => window.location.reload());
|
||||
async function handleSave() {
|
||||
saved = false;
|
||||
try {
|
||||
await putElevator(config);
|
||||
saved = true;
|
||||
setTimeout(() => { saved = false; window.location.reload(); }, 1500);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="hero is-light">
|
||||
<div class="hero-body">
|
||||
<p class="title has-text-dark">🛗 电梯配置</p>
|
||||
<p class="subtitle has-text-grey">电梯运行参数设置</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="section">
|
||||
<div class="container">
|
||||
{#if loading}
|
||||
<p class="has-text-centered"><span class="loader is-medium"></span></p>
|
||||
{:else if error}
|
||||
<p class="notification is-danger">{error}</p>
|
||||
{:else}
|
||||
{#if saved}
|
||||
<div class="notification is-success">✅ 保存成功!</div>
|
||||
{/if}
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">运行参数</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" bind:checked={config.enabled} />
|
||||
启用电梯功能
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="field">
|
||||
<label class="label">超时时间 (ms)</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.timeoutMs} placeholder="8000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<header class="card-header"><p class="card-header-title">端口信息</p></header>
|
||||
<div class="card-content">
|
||||
<p><strong>端口数:</strong>{config.portCount ?? 0}</p>
|
||||
<p><strong>端口列表:</strong>{config.portHintList && config.portHintList.length > 0 ? config.portHintList.join(', ') : '未配置'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">运行日志</p></header>
|
||||
<div class="card-content" style="max-height:200px;overflow-y:auto;">
|
||||
<article class="message is-small">
|
||||
<div class="message-body">
|
||||
<p><span class="has-text-weight-bold">[{new Date().toLocaleTimeString()}]</span> 电梯运行状态正常</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="card-content has-text-right mt-4">
|
||||
<button class="button is-primary" on:click={handleSave}>💾 保存配置</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.loader { border-top-color: #3273dc; animation-duration: 1s; }
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
import { onMount } from 'svelte';
|
||||
import { getLan, putLan } from '../lib/api.js';
|
||||
import styles from './LanPage.module.svelte';
|
||||
|
||||
let config = $state({});
|
||||
let loading = $state(false);
|
||||
let error = $state(null);
|
||||
let saved = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
@@ -18,6 +19,107 @@ onMount(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave() {
|
||||
putLan(config).then(() => window.location.reload());
|
||||
async function handleSave() {
|
||||
saved = false;
|
||||
try {
|
||||
await putLan(config);
|
||||
saved = true;
|
||||
setTimeout(() => { saved = false; window.location.reload(); }, 1500);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="hero is-light">
|
||||
<div class="hero-body">
|
||||
<p class="title has-text-dark">🌐 LAN网络配置</p>
|
||||
<p class="subtitle has-text-grey">局域网网络参数设置</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="section">
|
||||
<div class="container">
|
||||
{#if loading}
|
||||
<p class="has-text-centered"><span class="loader is-medium"></span></p>
|
||||
{:else if error}
|
||||
<p class="notification is-danger">{error}</p>
|
||||
{:else}
|
||||
{#if saved}
|
||||
<div class="notification is-success">✅ 保存成功!</div>
|
||||
{/if}
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">网络参数</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">IP地址</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.ip} placeholder="192.168.1.100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">子网掩码</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.mask} placeholder="255.255.255.0" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">网关</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.gateway} placeholder="192.168.1.1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">DNS</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.dns} placeholder="8.8.8.8" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-content has-text-right">
|
||||
<button class="button is-primary" on:click={handleSave}>💾 保存配置</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">DHCP设置</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" bind:checked={config.dhcp} />
|
||||
启用DHCP
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="field">
|
||||
<label class="label">DHCP地址池</label>
|
||||
<div class="control">
|
||||
<input type="text" class="input" bind:value={config.dhcp_pool} placeholder="192.168.1.50-200" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<header class="card-header"><p class="card-header-title">网络状态</p></header>
|
||||
<div class="card-content">
|
||||
<p><strong>MAC地址:</strong>{config.mac || '未获取'}</p>
|
||||
<p><strong>DHCP服务器:</strong>{config.dhcp_server || '无'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.loader { border-top-color: #3273dc; animation-duration: 1s; }
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import { onMount } from 'svelte';
|
||||
import { getMainRelay, putMainRelay } from '../lib/api.js';
|
||||
|
||||
let config = $state({});
|
||||
let loading = $state(false);
|
||||
let error = $state(null);
|
||||
let saved = $state(false);
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
@@ -17,7 +19,97 @@ onMount(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave(event: Event) {
|
||||
event.preventDefault();
|
||||
putMainRelay(config).then(() => window.location.reload());
|
||||
async function handleSave() {
|
||||
saved = false;
|
||||
try {
|
||||
await putMainRelay(config);
|
||||
saved = true;
|
||||
setTimeout(() => { saved = false; window.location.reload(); }, 1500);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="hero is-light">
|
||||
<div class="hero-body">
|
||||
<p class="title has-text-dark">🔌 主继电器控制</p>
|
||||
<p class="subtitle has-text-grey">主继电器状态与参数设置</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="section">
|
||||
<div class="container">
|
||||
{#if loading}
|
||||
<p class="has-text-centered"><span class="loader is-medium"></span></p>
|
||||
{:else if error}
|
||||
<p class="notification is-danger">{error}</p>
|
||||
{:else}
|
||||
{#if saved}
|
||||
<div class="notification is-success">✅ 保存成功!</div>
|
||||
{/if}
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">继电器状态</p></header>
|
||||
<div class="card-content">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="content">
|
||||
<p><strong>状态:</strong><span class="has-text-success">在线 (模拟)</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="buttons">
|
||||
<button class="button is-warning" on:click={() => config.state = true}>闭合</button>
|
||||
<button class="button is-danger" on:click={() => config.state = false}>断开</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<header class="card-header"><p class="card-header-title">延迟参数 (ms)</p></header>
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">关闭延迟</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.lockDelayMs} placeholder="5000" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">开启延迟</label>
|
||||
<div class="control">
|
||||
<input type="number" class="input" bind:value={config.unlockDelayMs} placeholder="2000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header"><p class="card-header-title">运行日志</p></header>
|
||||
<div class="card-content" style="max-height:200px;overflow-y:auto;">
|
||||
<article class="message is-small">
|
||||
<div class="message-body">
|
||||
<p><span class="has-text-weight-bold">[{new Date().toLocaleTimeString()}]</span> 继电器运行状态正常</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="card-content has-text-right mt-4">
|
||||
<button class="button is-primary" on:click={handleSave}>💾 保存配置</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.loader { border-top-color: #3273dc; animation-duration: 1s; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user