Documentation

EdgeOS 工业大脑 UI 开发实操指南

基于 shadcn/ui 的工业级工程标准与实现规范

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.vuenuxt.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