EdgeOS 工业大脑 UI 开发实操指南 (基于 v4.0 标准)
文档定位
本文档是 EdgeOS UI 设计规范的 工程化落地版本 ,从“视觉指南”进化为 “工业级工程标准” 。它针对 EdgeOS 这种高实时性、高可靠性的边缘计算系统,在 shadcn/ui (Radix UI) 的基础上进行了深度“降噪”处理,确保界面在极端工业环境下(如高温、震动、弱光、触控手套操作)依然保持高可读性和操作精确度。
⚠️ 工业控制级 UI 强制约束(v4.5 增强)
在 v4.0 标准基础上,新增以下“控制级约束”,用于将系统从“工业风 UI”升级为“工业控制界面(SCADA级)”。
A. UI 层级定义(新增)
| 层级 | 说明 |
|---|---|
| 展示层 | Table / Card / Form |
| 状态层 | Status / Quality / Alarm |
| 运行层 | Channel / Device / Topology |
| 控制层 | 操作 / 风险控制 / 审计 |
👉 禁止跳层设计(例如:直接在展示层做控制操作)
B. 状态优先原则(强制)
UI 不允许出现:
- 只展示“在线/离线”
- 无质量指标
- 无趋势信息
👉 必须包含:
- Latency(延迟)
- Loss(丢包)
- Quality(质量评分)
C. 操作分级机制(新增)
| 等级 | 操作类型 | UI要求 |
|---|---|---|
| L1 | 查看 | 无提示 |
| L2 | 编辑 | Toast |
| L3 | 下发 | Confirm |
| L4 | 停机/重启 | Confirm + 倒计时 |
| L5 | 恢复/危险 | 输入确认词 |
1. 核心布局架构 (Layout Implementation)
在工业场景中,侧边栏(Sidebar)不仅仅是导航,更是系统状态的常驻监控区。布局必须保持稳定,避免因内容滚动导致的界面晃动。
1.1 整体布局结构
┌─────────────────────────────────────────────────────────────────────────────┐
│ App Container │
│ ┌──────────────┬─────────────────────────────────────────────────────────┐ │
│ │ │ Header (56px) │ │
│ │ Sidebar │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ (240px) │ │ 面包屑 > 当前页面 [全局搜索] [用户] [设置] │ │ │
│ │ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌────────┐ │ │ │
│ │ │ Logo │ │ Main Content (动态路由) │ │
│ │ └────────┘ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │ │
│ │ ┌────────┐ │ │ Page Container (padding: 20px 24px) │ │ │
│ │ │ Nav │ │ │ │ │ │
│ │ │ Menu │ │ │ [页面内容] │ │ │
│ │ │ │ │ │ │ │ │
│ │ └────────┘ │ │ │ │ │
│ │ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌────────┐ │ │ │
│ │ │Status │ │ Footer (可选,32px,显示连接状态/版本号) │ │
│ │ │Footer │ │ │ │
│ │ └────────┘ │ │ │
│ └──────────────┴─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
1.2 侧边栏实现规范
| 属性 | 值 | 说明 |
|---|---|---|
| 宽度 | 240px |
固定宽度,不随内容收缩 |
| 背景 | bg-white |
纯白背景 |
| 右边框 | border-r border-slate-200 |
1px 实线,禁止使用阴影 |
| Logo 区域高度 | 56px |
与 Header 高度对齐 |
| Logo 下边框 | border-b border-slate-200 |
分割线 |
| 菜单项高度 | 40px |
足够的手指触控区域(工业手套适配) |
| 菜单项内边距 | px-4 py-2 |
左右 16px |
| 菜单项圆角 | rounded-lg |
圆角 |
| 菜单项悬停背景 | hover:bg-slate-50 |
浅灰反馈 |
| 菜单项激活态 | border-l-2 border-l-sky-500 bg-slate-50 |
左侧蓝色指示条 |
| 菜单图标尺寸 | w-5 h-5 |
18-20px |
| 图标与文字间距 | gap-3 |
12px |
| 底部状态区 | mt-auto border-t border-slate-200 p-4 |
显示设备连接状态、版本号 |
侧边栏实现代码:
<!-- components/layout/AppSidebar.vue -->
<template>
<aside class="fixed left-0 top-0 z-30 h-screen w-64 border-r border-slate-200 bg-white flex flex-col">
<!-- Logo 区域 -->
<div class="flex h-14 items-center border-b border-slate-200 px-4">
<img src="/logo.svg" alt="EdgeOS" class="h-8" />
<span class="ml-2 text-lg font-semibold text-slate-900">EdgeOS</span>
</div>
<!-- 导航菜单 -->
<nav class="flex-1 space-y-1 p-2">
<NuxtLink
v-for="item in navItems"
:key="item.path"
:to="item.path"
class="flex items-center gap-3 rounded-none px-4 py-2 text-sm text-slate-700 hover:bg-slate-50"
:class="{ 'border-l-2 border-l-sky-500 bg-slate-50 text-slate-900': isActive(item.path) }"
>
<component :is="item.icon" class="h-5 w-5" />
{{ item.name }}
</NuxtLink>
</nav>
<!-- 底部状态区 -->
<div class="border-t border-slate-200 p-4">
<div class="flex items-center gap-2">
<StatusIndicator status="online" />
<span class="text-xs text-slate-500">网关已连接</span>
</div>
<p class="mt-2 text-xs text-slate-400">v0.0.1</p>
</div>
</aside>
</template>
1.3 页头 (Header) 实现规范
| 属性 | 值 | 说明 |
|---|---|---|
| 高度 | 56px |
与侧边栏 Logo 区域对齐 |
| 背景 | bg-white |
纯白背景 |
| 下边框 | border-b border-slate-200 |
分割线 |
| 内边距 | px-6 |
左右 24px |
| 布局 | flex items-center justify-between |
左右分布 |
面包屑规范:
- 强制显示完整路径,不省略中间层级
- 分隔符统一使用 斜杠
/(非>或») - 当前页面不可点击,文字色
text-slate-900 - 上级路径可点击,文字色
text-slate-500 hover:text-slate-900
全局搜索 (Command Palette):
- 触发方式:点击搜索框或快捷键
Ctrl+K(Mac:Cmd+K) - 搜索范围:设备、协议、配置文件、文档
- 使用 shadcn
Command组件实现 - 搜索框占位符:
搜索设备、协议或文档... (Ctrl+K)
页头实现代码:
<!-- components/layout/AppHeader.vue -->
<template>
<header class="fixed left-64 right-0 top-0 z-20 h-14 border-b border-slate-200 bg-white">
<div class="flex h-full items-center justify-between px-6">
<!-- 面包屑 -->
<Breadcrumb :items="breadcrumbs" separator="/" />
<!-- 右侧操作区 -->
<div class="flex items-center gap-4">
<!-- 全局搜索 -->
<button
@click="openCommandPalette"
class="flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-sm text-slate-500 hover:bg-slate-50"
>
<Search class="h-4 w-4" />
<span class="hidden sm:inline">搜索...</span>
<kbd class="hidden text-xs text-slate-400 sm:inline-block">Ctrl+K</kbd>
</button>
<!-- 用户菜单 -->
<DropdownMenu>
<DropdownMenuTrigger as-child>
<button class="flex h-8 w-8 items-center justify-center rounded-full bg-slate-100">
<User class="h-4 w-4 text-slate-600" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="rounded-lg border-slate-200">
<DropdownMenuItem>个人设置</DropdownMenuItem>
<DropdownMenuItem>退出登录</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</header>
</template>
1.4 工业运行态信息条(新增,强制)
Header 不仅是导航栏,必须升级为“系统运行状态条”。
必须增加字段:
| 字段 | 示例 |
|---|---|
| 系统状态 | RUNNING |
| 在线设备 | 128 |
| 告警数 | 3 |
| 平均延迟 | 32ms |
| 丢包率 | 0.2% |
UI 示例:
| RUNNING | Devices:128 | Alarm:3 | Latency:32ms | Loss:0.2% |
实现建议:
<div class="flex items-center gap-4 text-xs font-mono text-slate-500">
<span>RUNNING</span>
<span>Devices: {{ devices }}</span>
<span>Alarm: {{ alarms }}</span>
<span>Latency: {{ latency }}ms</span>
<span>Loss: {{ loss }}%</span>
</div>
👉 该信息条属于工业系统核心,不得省略
2. 工业级数据表 (Industrial Data Table)
表格是 EdgeOS 的资产管理核心,必须处理每页 50-100 行的高密度数据,支持虚拟滚动以优化性能。
2.1 高密度样式实现
| 模式 | 行高 | 适用场景 |
|---|---|---|
| 常规模式 | h-10 (40px) |
默认展示,兼顾可读性与密度 |
| 紧凑模式 | h-8 (32px) |
监控大屏、高级用户、高密度数据展示 |
| 宽松模式 | h-12 (48px) |
触控屏、工业手套操作环境 |
2.2 表格全局配置
<!-- 使用 shadcn Table 组件,配合自定义样式 -->
<Table class="border-collapse border border-slate-200">
<TableHeader class="bg-slate-50">
<TableRow class="border-b border-slate-200 hover:bg-transparent">
<TableHead class="h-10 px-3 text-xs font-semibold text-slate-500">
设备名称
</TableHead>
<!-- 更多列... -->
</TableRow>
</TableHeader>
<TableBody>
<TableRow
v-for="row in data"
:key="row.id"
class="border-b border-slate-200 hover:bg-slate-50"
:class="{ 'border-l-2 border-l-sky-500 bg-sky-50/50': selectedId === row.id }"
>
<TableCell class="px-3 py-0">
<EdgeDeviceCell :device="row" />
</TableCell>
<!-- 更多单元格... -->
</TableRow>
</TableBody>
</Table>
2.3 设备单元格封装 (EdgeDeviceCell)
<!-- components/edge/EdgeDeviceCell.vue -->
<template>
<div class="flex flex-col items-start gap-0.5 py-1.5">
<span class="max-w-[200px] truncate text-sm font-medium leading-none text-slate-900">
{{ device.name }}
</span>
<span class="font-mono text-[10px] leading-none text-slate-500">
ID: {{ device.id }}
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
device: {
name: string
id: string
}
}>()
</script>
2.4 表格列配置规范
| 列类型 | 对齐 | 宽度 | 特殊处理 |
|---|---|---|---|
| 设备信息 | 左对齐 | min-w-[180px] |
双行布局,名称可换行截断 |
| 协议类型 | 左对齐 | 100px |
使用 EdgeProtocolBadge 组件 |
| 状态 | 左对齐 | 80px |
使用 StatusIndicator 组件 |
| IP 地址 | 左对齐 | 120px |
等宽字体 font-mono text-xs |
| 最后通信 | 左对齐 | 140px |
相对时间格式(如“5分钟前”) |
| 操作 | 右对齐 | 100px |
固定在右侧,包含编辑/删除图标 |
2.5 表格性能优化
<!-- 大数据量时启用虚拟滚动 -->
<script setup lang="ts">
import { useVirtualizer } from '@tanstack/vue-virtual'
const parentRef = ref<HTMLElement>()
const rowVirtualizer = useVirtualizer({
count: data.value.length,
getScrollElement: () => parentRef.value,
estimateSize: () => 40, // 行高 40px
overscan: 10,
})
</script>
2.6 工业监控型表格扩展(强制)
当前表格为“资产管理表格”,必须升级为“监控型表格”。
新增字段:
| 字段 | 说明 |
|---|---|
| Quality | 通讯质量评分 |
| Latency | 延迟 |
| Loss | 丢包 |
| Retry | 重试次数 |
| Last Error | 最近错误 |
2.7 质量条组件(新增)
<!-- components/edge/QualityBar.vue -->
<template>
<div class="h-1.5 w-full bg-slate-200">
<div
class="h-full"
:style="{
width: value + '%',
background: color
}"
/>
</div>
</template>
2.8 行状态强化(工业重点)
<TableRow
:class="[
row.status === 'fault' && 'border-l-2 border-l-red-500',
row.status === 'critical' && 'border-l-2 border-l-red-700 bg-red-50/30'
]"
>
👉 工业系统必须做到:故障一眼可见(边框优先于颜色块)
---
## 3. 实时监控组件 (Monitoring Components)
### 3.1 状态指示器 (The "Pulse" Standard)
工业状态不仅靠颜色,还要靠“动态感知”,让操作人员在余光中也能感知状态变化。
**色彩语义**:
| 状态 | 颜色 | 动画 | 语义 |
| :--- | :--- | :--- | :--- |
| **运行中** | `emerald-500` | `animate-pulse` | 系统正常、逻辑通过、在线 |
| **待机/离线** | `amber-500` | 无 | 离线/待机、非致命警告、连接中断 |
| **故障** | `red-500` | `animate-pulse` | 指令超时、硬件故障、通讯中断 |
| **未知** | `slate-400` | 无 | 状态未上报、初始化中 |
**双层脉冲动画实现**:
```vue
<!-- components/edge/StatusIndicator.vue -->
<template>
<div class="relative flex h-2 w-2">
<!-- 外层脉冲环 (仅运行和故障状态显示) -->
<span
v-if="showPulse"
class="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
:class="pulseColorClass"
></span>
<!-- 内层实心点 -->
<span
class="relative inline-flex h-2 w-2 rounded-full"
:class="dotColorClass"
></span>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
status: 'running' | 'standby' | 'fault' | 'unknown'
}>()
const showPulse = computed(() => props.status === 'running' || props.status === 'fault')
const dotColorClass = computed(() => {
switch (props.status) {
case 'running': return 'bg-emerald-500'
case 'standby': return 'bg-amber-500'
case 'fault': return 'bg-red-500'
default: return 'bg-slate-400'
}
})
const pulseColorClass = computed(() => {
switch (props.status) {
case 'running': return 'bg-emerald-400'
case 'fault': return 'bg-red-400'
default: return ''
}
})
</script>
3.1.1 状态传播模型(新增,核心)
工业系统状态不是独立存在,而是层级传播:
Point → Device → Channel → EdgeX
传播规则:
function worst(a, b) {
const priority = ['running', 'standby', 'fault', 'critical']
return priority.indexOf(a) > priority.indexOf(b) ? a : b
}
3.1.2 通道健康模型(新增) type ChannelHealth = { latency: number jitter: number loss: number quality: number }
3.1.3 通道状态组件(新增)
<template>
<div class="flex items-center gap-2">
<StatusIndicator :status="status" />
<div class="flex gap-3 text-[10px] font-mono text-slate-500">
<span>L {{ latency }}ms</span>
<span>J {{ jitter }}</span>
<span>P {{ loss }}%</span>
</div>
</div>
</template>
👉 这是工业 UI 与普通 UI 的分水岭
3.2 协议徽章 (Protocol Badges)
统一封装 <EdgeProtocolBadge protocol="modbus" />,根据协议类型自动匹配视觉样式。
| 协议 | 背景色 | 文字色 | 说明 |
|---|---|---|---|
| Modbus/TCP | bg-sky-500 |
white |
科技蓝,最常用 |
| Modbus/RTU | bg-sky-600 |
white |
深蓝,串口版本 |
| OPC UA | bg-indigo-500 |
white |
深靛蓝 |
| MQTT | bg-emerald-500 |
white |
翠绿色 |
| IEC 104 | bg-purple-500 |
white |
紫色 |
| BACnet | bg-amber-600 |
white |
琥珀色 |
| 自定义 | bg-slate-500 |
white |
默认灰色 |
实现代码:
<!-- components/edge/EdgeProtocolBadge.vue -->
<template>
<span
class="inline-flex items-center rounded-[2px] px-1.5 py-0.5 font-mono text-[10px] font-medium leading-none text-white"
:class="bgColorClass"
>
{{ displayName }}
</span>
</template>
<script setup lang="ts">
const props = defineProps<{
protocol: 'modbus' | 'modbus-rtu' | 'opcua' | 'mqtt' | 'iec104' | 'bacnet' | string
}>()
const protocolMap: Record<string, { name: string; color: string }> = {
modbus: { name: 'Modbus/TCP', color: 'bg-sky-500' },
'modbus-rtu': { name: 'Modbus/RTU', color: 'bg-sky-600' },
opcua: { name: 'OPC UA', color: 'bg-indigo-500' },
mqtt: { name: 'MQTT', color: 'bg-emerald-500' },
iec104: { name: 'IEC 104', color: 'bg-purple-500' },
bacnet: { name: 'BACnet', color: 'bg-amber-600' },
}
const config = computed(() => protocolMap[props.protocol] || { name: props.protocol.toUpperCase(), color: 'bg-slate-500' })
const displayName = computed(() => config.value.name)
const bgColorClass = computed(() => config.value.color)
</script>
3.3 实时数值卡片 (Metric Card)
用于展示关键指标(如 CPU 温度、压力值、电流等)。
<!-- components/edge/MetricCard.vue -->
<template>
<div class="border border-slate-200 bg-white p-4">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-slate-500">{{ label }}</span>
<StatusIndicator v-if="showStatus" :status="status" />
</div>
<div class="mt-2 flex items-baseline gap-1">
<span class="text-2xl font-semibold text-slate-900">{{ value }}</span>
<span class="text-sm text-slate-500">{{ unit }}</span>
</div>
<div v-if="trend" class="mt-2 flex items-center gap-1">
<ArrowUp v-if="trend > 0" class="h-3 w-3 text-emerald-500" />
<ArrowDown v-else class="h-3 w-3 text-red-500" />
<span class="text-xs" :class="trend > 0 ? 'text-emerald-600' : 'text-red-600'">
{{ Math.abs(trend) }}%
</span>
<span class="text-xs text-slate-400">较昨日</span>
</div>
</div>
</template>
工业增强要求:
- 必须支持报警阈值(高/低)
- 必须支持闪烁(超限)
- 必须支持历史趋势(可选)
<div :class="value > threshold ? 'animate-pulse text-red-500' : ''">
---
## 3.4 工业拓扑视图(Topology System)
工业系统必须具备设备拓扑结构:
EdgeX Gateway
├── Channel
│ ├── Device
│ ├── Points
---
### UI结构:
- 左:树结构(Tree)
- 右:设备详情 + 实时状态
---
### 状态传播可视化:
- 子节点故障 → 父节点染色
- Channel 故障 → 整体标红
---
### 实现建议:
```vue
<Tree :data="topology">
👉 没有拓扑视图,不算工业系统
---
## 4. 关键交互:防抖与反馈 (Safe Interaction)
在边缘计算系统中,**下发配置**是高危操作,UI 必须提供绝对的心理预期和安全屏障。
### 4.1 危险操作对话框
所有涉及 `Delete`、`Stop`、`Reboot`、`Reset` 的按钮,必须弹出直角对话框进行二次确认。
**实现规范**:
| 属性 | 值 |
| :--- | :--- |
| 圆角 | `rounded-lg` |
| 边框 | `1px solid #E2E8F0` |
| 宽度 | `400px` |
| 遮罩 | `bg-black/50`,无模糊 |
| 标题 | `text-slate-900 font-semibold` |
| 描述 | `text-slate-500 text-sm` |
**倒计时锁定**:确认按钮在对话框弹出后的前 `2s` 处于禁用状态,防止用户误触。
```vue
<!-- components/edge/DangerDialog.vue -->
<template>
<DialogRoot v-model:open="open">
<DialogPortal>
<DialogOverlay class="fixed inset-0 bg-black/50" />
<DialogContent class="fixed left-1/2 top-1/2 w-[400px] -translate-x-1/2 -translate-y-1/2 rounded-lg border border-slate-200 bg-white p-0">
<DialogHeader class="border-b border-slate-200 p-4">
<DialogTitle class="text-lg font-semibold text-slate-900">
确认{{ actionName }}
</DialogTitle>
</DialogHeader>
<div class="p-4">
<DialogDescription class="text-sm text-slate-500">
此操作将{{ actionDescription }},是否继续?
</DialogDescription>
</div>
<DialogFooter class="flex items-center justify-end gap-2 border-t border-slate-200 p-4">
<Button variant="outline" @click="open = false" class="rounded-lg">
取消
</Button>
<Button
variant="destructive"
:disabled="countdown > 0"
@click="confirm"
class="rounded-lg"
>
{{ countdown > 0 ? `确认 (${countdown})` : '确认' }}
</Button>
</DialogFooter>
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>
<script setup lang="ts">
const countdown = ref(2)
let timer: NodeJS.Timeout
watch(open, (newVal) => {
if (newVal) {
countdown.value = 2
timer = setInterval(() => {
if (countdown.value > 0) countdown.value--
else clearInterval(timer)
}, 1000)
} else {
clearInterval(timer)
}
})
</script>
4.5 L5级危险操作(新增)
对于以下操作:
- 恢复出厂
- 批量删除
- 停止采集系统
必须增加“输入确认词”:
<input placeholder="输入 DELETE 确认操作" />
4.6 操作审计(新增)
所有 L3+ 操作必须记录:
type AuditLog = {
user: string
action: string
target: string
timestamp: number
}
👉 工业系统必须具备可追溯性
---
### 4.2 Toast 通知规范
| 类型 | 持续时间 | 操作 | 样式 |
| :--- | :--- | :--- | :--- |
| 成功 | 2s | 无 | 绿色边框,绿色文字 |
| 错误 | 5s | 重试按钮 | 红色边框,红色文字 |
| 警告 | 4s | 查看详情 | 橙色边框 |
| 信息 | 2s | 无 | 蓝色边框 |
```vue
<!-- 使用 shadcn toast 组件 -->
<Toast v-if="toast" :variant="toast.type" class="rounded-lg border-l-4">
<div class="flex items-center justify-between">
<span>{{ toast.message }}</span>
<button v-if="toast.onRetry" @click="toast.onRetry" class="text-sm underline">
重试
</button>
</div>
</Toast>
4.3 防抖指令封装
工业协议搜索、参数下发指令必须经过防抖处理,避免频繁请求。
// composables/useDebounce.ts
export function useDebounce<T extends (...args: any[]) => any>(
fn: T,
delay: number = 300
) {
let timer: NodeJS.Timeout | null = null
return (...args: Parameters<T>) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => fn(...args), delay)
}
}
// 使用示例
const searchDevices = useDebounce((keyword: string) => {
api.searchDevices(keyword)
}, 300)
4.4 节流指令封装
用于滚动加载、窗口 resize 等高频事件。
// composables/useThrottle.ts
export function useThrottle<T extends (...args: any[]) => any>(
fn: T,
delay: number = 200
) {
let lastCall = 0
return (...args: Parameters<T>) => {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
fn(...args)
}
}
}
5. 性能与工程化建议
5.1 Tailwind 全局强制覆写
在 globals.css 中加入以下“硬核”指令,彻底根除 shadcn 默认的互联网属性(圆角、阴影、外发光)。
/* assets/css/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* 1. 强制所有交互组件取消 Ring 偏移,改用内边框 */
*:focus {
outline: none !important;
}
.focus-visible\:ring-2,
*:focus-visible {
--tw-ring-offset-width: 0px !important;
--tw-ring-width: 0px !important;
--tw-ring-offset-color: transparent !important;
--tw-ring-color: transparent !important;
box-shadow: none !important;
}
/* 2. 按钮点击无阴影 */
button:focus,
button:focus-visible {
box-shadow: none !important;
outline: none !important;
}
/* 3. 输入框 Focus 使用边框色替代 Ring */
input:focus,
select:focus,
textarea:focus {
border-color: #0EA5E9 !important;
box-shadow: none !important;
outline: none !important;
}
/* 4. 工业级表格滚动条 */
.scrollbar-industrial {
scrollbar-width: thin;
scrollbar-color: #CBD5E1 transparent;
}
.scrollbar-industrial::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollbar-industrial::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-industrial::-webkit-scrollbar-thumb {
background: #CBD5E1;
border-radius: 0px;
}
.scrollbar-industrial::-webkit-scrollbar-thumb:hover {
background: #94A3B8;
}
}
@layer utilities {
/* 5. 强制无圆角覆盖 */
.rounded-none-force {
border-radius: 0px !important;
}
/* 6. 强制无阴影覆盖 */
.shadow-none-force {
box-shadow: none !important;
}
}
5.4 工业暗色模式(强制推荐)
默认应提供暗色工业主题(参考 :contentReference[oaicite:0]{index=0} 与 :contentReference[oaicite:1]{index=1})
色板:
:root.dark {
--bg-base: #0B0F14;
--bg-panel: #111827;
--border: #374151;
--text-primary: #E5E7EB;
}
5.5 工业视觉约束(必须)
禁止:
阴影(shadow)
渐变(gradient)
模糊(backdrop-blur)
允许:
边框层级
颜色语义
状态动画(pulse)
---
### 5.2 字体加载策略
| 用途 | 字体 | 说明 |
| :--- | :--- | :--- |
| UI 文字 | 系统默认 `sans-serif` | 优先 `Inter`,降级 `-apple-system`, `BlinkMacSystemFont` |
| 代码/数值 | `JetBrains Mono` | 数字 `0` 与字母 `O`、数字 `1` 与字母 `l` 高度可区分 |
| 后备字体 | `monospace` | 系统等宽字体 |
**字体引入**:
```css
/* assets/css/fonts.css */
@import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500;600&display=swap');
:root {
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
}
body {
font-family: var(--font-sans);
}
.font-mono,
.device-id,
.protocol-badge,
.ip-address,
.metric-value {
font-family: var(--font-mono) !important;
font-feature-settings: 'tnum' 1, 'zero' 1;
}
5.3 shadcn/ui 组件改造清单
| 组件 | 改造项 | 改造后值 |
|---|---|---|
Button |
rounded-md |
rounded-lg |
Button |
focus-visible:ring-2 |
移除 |
Input |
rounded-md |
rounded-lg |
Input |
focus-visible:ring-2 |
移除 |
Card |
rounded-lg |
rounded-lg |
Card |
shadow-sm |
移除 |
Dialog |
rounded-lg |
rounded-lg |
Dialog |
shadow-lg |
移除 |
DropdownMenu |
rounded-md |
rounded-lg |
DropdownMenu |
shadow-lg |
移除 |
Table |
rounded-lg |
rounded-lg |
Tabs |
激活样式 | 下划线指示器,高度 2px |
全局配置覆盖 (app.vue 或 nuxt.config.ts):
// nuxt.config.ts
export default defineNuxtConfig({
ui: {
button: {
default: {
class: 'rounded-lg focus-visible:ring-0',
},
solid: {
class: 'rounded-lg',
},
outline: {
class: 'rounded-lg',
},
},
input: {
default: {
class: 'rounded-lg focus-visible:ring-0',
},
},
card: {
default: {
class: 'rounded-lg shadow-none border border-slate-200',
},
},
},
})
6. 验收标准清单 (Ready for Prod)
在交付前,请逐项检查以下内容:
视觉层面
- 圆角设计统一:检查所有 UI 元素是否使用了一致的
rounded-lg圆角 - 阴影归零:全局搜索
shadow-,确保无残留阴影 - 边框统一:所有边框使用
border-slate-200,厚度1px - 品牌蓝克制:
#0EA5E9仅出现在激活态、主按钮、协议标签 - Mono 覆盖:IP 地址、设备 ID、MAC 地址、Modbus 地址是否都使用了等宽字体?
- 对比度检查:确保辅助文字(
text-slate-500)在暗光工业屏下依然清晰可见(对比度 ≥ 4.5:1)
组件层面
- EdgeDeviceCell:设备名称 + ID 双行布局正常
- StatusIndicator:运行/故障状态有脉冲动画
- EdgeProtocolBadge:常见协议有对应颜色
- 表格:表头背景
#F1F5F9,行高 40px,悬停背景变化 - 按钮:高度 32px/28px,直角,无外发光
交互层面
- 危险操作二次确认:Delete/Stop 等操作弹出确认对话框
- 倒计时锁定:确认按钮在 2s 内不可点击
- 防抖保护:搜索输入有 300ms 防抖
- Toast 反馈:错误 Toast 持续 5s 且包含重试按钮
- 快捷键:
Ctrl+K可打开命令面板
性能层面
- 虚拟滚动:表格行数 > 200 时启用虚拟滚动
- 路由懒加载:页面组件使用
defineAsyncComponent - 图表懒加载:ECharts 等大型库动态导入
- 首屏加载:< 2s (3G 网络模拟)
- 内存无泄漏:组件销毁时清理定时器、监听器
响应式层面
- 手机端 (< 768px):表格可横向滚动,侧边栏收缩为底部菜单
- 平板端 (768px - 1024px):表单水平转垂直,卡片双列布局
- 大屏端 (> 1440px):内容居中或充分利用宽屏
工程化层面
- Tailwind 覆写生效:
globals.css中的强制指令已应用 - 字体加载正确:
JetBrains Mono在数值字段生效 - TypeScript 类型完整:无
any类型滥用 - ESLint 通过:无警告或错误
🚨 工业控制级新增验收项(必须)
运行态
- 是否展示 Latency / Loss / Quality?
- 是否存在状态传播逻辑?
- 是否支持通道健康监控?
控制安全
- 是否存在 L5 输入确认?
- 是否记录操作日志?
- 是否避免误触?
拓扑能力
- 是否具备设备拓扑视图?
- 是否支持状态染色传播?
👉 不满足以上条件,不可定义为“工业级 UI”
附录:常用工具与资源
A. 开发环境配置
# 创建 Nuxt 3 + shadcn/vue 项目
npx nuxi@latest init edgeos-web
cd edgeos-web
npm install
# 安装 shadcn/vue
npx shadcn-vue@latest init
# 安装额外依赖
npm install @tanstack/vue-virtual lucide-vue-next
B. 推荐 VS Code 插件
| 插件 | 用途 |
|---|---|
| Tailwind CSS IntelliSense | Tailwind 类名提示 |
| Vue - Official | Vue 3 语法支持 |
| Prettier | 代码格式化 |
| ESLint | 代码检查 |
C. 参考案例
| 产品 | 可借鉴点 |
|---|---|
| Grafana | 线构风格、数据可视化、暗色主题 |
| VSCode | 直角设计、高信息密度、命令面板 |
| 西门子 WinCC | 工业蓝配色、状态指示语义 |
| Proxmox VE | 服务器管理界面、表格设计 |
文档版本:v4.5-工业控制增强版 最后更新:2026-04-21 维护者:EdgeOS UI 团队 适用框架:Nuxt 3 + shadcn/vue