Sfoglia il codice sorgente

fix: 修改文件名大小写

nahida 9 mesi fa
parent
commit
9c841273bf
1 ha cambiato i file con 1646 aggiunte e 0 eliminazioni
  1. 1646 0
      app/(other)/test7/page.tsx

+ 1646 - 0
app/(other)/test7/page.tsx

@@ -0,0 +1,1646 @@
+"use client"
+
+import "antd/dist/reset.css"
+
+import {useEffect, useMemo, useRef, useState} from "react"
+import dynamic from "next/dynamic"
+import type {MenuProps} from "antd"
+import {
+  Badge,
+  Button,
+  Card,
+  Col,
+  Collapse,
+  Descriptions,
+  Divider,
+  Drawer,
+  Form,
+  Input,
+  Layout,
+  Menu,
+  Modal,
+  Popover,
+  Progress,
+  Row,
+  Segmented,
+  Select,
+  Slider,
+  Space,
+  Statistic,
+  Steps,
+  Switch,
+  Table,
+  Tabs,
+  Tag,
+  Timeline,
+  Typography,
+  Upload,
+} from "antd"
+import {
+  Activity,
+  Bell,
+  Box,
+  Boxes,
+  Check,
+  CheckCircle2,
+  CloudUpload,
+  Cog,
+  Database,
+  Factory,
+  FileJson2,
+  Globe,
+  Layers,
+  LinkIcon,
+  Lock,
+  Network,
+  Package,
+  Radar,
+  RefreshCw,
+  Send,
+  Server,
+  Settings,
+  ShieldCheck,
+  SignalHigh,
+  Sprout,
+  UploadIcon,
+  Users,
+} from "lucide-react"
+import globalMessage from "@/app/_modules/globalMessage";
+
+const { Header, Sider, Content } = Layout
+const { TextArea } = Input
+const { Title, Text } = Typography
+
+// Lazy components that require browser APIs
+const DeviceMap = dynamic(() => import("./components/device-map"), { ssr: false })
+const RealtimeLine = dynamic(() => import("./components/realtime-line"), { ssr: false })
+const QueueGauge = dynamic(() => import("./components/queue-gauge"), { ssr: false })
+
+type Project = {
+  id: string
+  name: string
+  description?: string
+  createdAt: string
+}
+type Product = {
+  id: string
+  name: string
+  protocol: "TCP" | "UDP" | "LWM2M" | "MQTTS" | "HTTP/HTTPS"
+  projectId: string
+  thirdParty: boolean
+  authToken?: string
+}
+type ThingModel = {
+  id: string
+  productId: string
+  json: string
+}
+type Device = {
+  id: string
+  name: string
+  productId: string
+  projectId: string
+  status: "online" | "offline"
+  lastSeen?: string
+  lat: number
+  lng: number
+}
+
+type Telemetry = {
+  ts: number
+  value: number
+  deviceId: string
+}
+
+const initialProjects: Project[] = [
+  { id: "p1", name: "城市燃气", description: "管网与阀井监测", createdAt: new Date().toISOString() },
+  { id: "p2", name: "城市水务", description: "供排水管线监测", createdAt: new Date().toISOString() },
+]
+const initialProducts: Product[] = [
+  { id: "prod1", name: "压力传感器A", protocol: "MQTTS", projectId: "p1", thirdParty: false },
+  { id: "prod2", name: "井盖监测B", protocol: "HTTP/HTTPS", projectId: "p1", thirdParty: true, authToken: "TP-XXXX" },
+  { id: "prod3", name: "流量计C", protocol: "LWM2M", projectId: "p2", thirdParty: false },
+]
+const initialThingModels: ThingModel[] = [
+  {
+    id: "tm1",
+    productId: "prod1",
+    json: JSON.stringify(
+      {
+        properties: [
+          { id: "pressure", name: "压力", type: "number", unit: "kPa" },
+          { id: "battery", name: "电量", type: "number", unit: "%" },
+        ],
+        events: [{ id: "alarm", name: "告警", level: "high" }],
+        services: [{ id: "reset", name: "复位", in: {}, out: {} }],
+      },
+      null,
+      2,
+    ),
+  },
+]
+const initialDevices: Device[] = [
+  {
+    id: "dev-001",
+    name: "压力A-001",
+    productId: "prod1",
+    projectId: "p1",
+    status: "online",
+    lastSeen: new Date().toISOString(),
+    lat: 31.231706,
+    lng: 121.472644,
+  },
+  {
+    id: "dev-002",
+    name: "井盖B-002",
+    productId: "prod2",
+    projectId: "p1",
+    status: "offline",
+    lat: 31.220706,
+    lng: 121.462644,
+  },
+  {
+    id: "dev-003",
+    name: "流量C-003",
+    productId: "prod3",
+    projectId: "p2",
+    status: "online",
+    lastSeen: new Date().toISOString(),
+    lat: 31.241706,
+    lng: 121.482644,
+  },
+]
+
+function useInterval(callback: () => void, delay: number | null) {
+  const savedRef = useRef<() => void>(null)
+  useEffect(() => {
+    savedRef.current = callback
+  }, [callback])
+  useEffect(() => {
+    if (delay === null) return
+    const id = setInterval(() => savedRef.current && savedRef.current(), delay)
+    return () => clearInterval(id)
+  }, [delay])
+}
+
+export default function Page() {
+  const [collapsed, setCollapsed] = useState(false)
+  const [activeKey, setActiveKey] = useState<string>("overview")
+
+  // Core states
+  const [projects, setProjects] = useState<Project[]>(initialProjects)
+  const [products, setProducts] = useState<Product[]>(initialProducts)
+  const [thingModels, setThingModels] = useState<ThingModel[]>(initialThingModels)
+  const [devices, setDevices] = useState<Device[]>(initialDevices)
+
+  const [telemetry, setTelemetry] = useState<Telemetry[]>([])
+  const [backlog, setBacklog] = useState<number>(8)
+  const [throughput, setThroughput] = useState<number>(120)
+  const [alerts, setAlerts] = useState<number>(1)
+  const [todayCount, setTodayCount] = useState<number>(3200)
+
+  const [logs, setLogs] = useState<
+    { key: string; time: string; deviceId: string; product: string; msg: string; level: "INFO" | "WARN" | "ERROR" }[]
+  >([])
+
+  // UI states for feedback
+  const [projectModalOpen, setProjectModalOpen] = useState(false)
+  const [productModalOpen, setProductModalOpen] = useState(false)
+  const [deviceModalOpen, setDeviceModalOpen] = useState(false)
+  const [cmdModal, setCmdModal] = useState<{ open: boolean; device?: Device }>({ open: false })
+  const [dataDrawer, setDataDrawer] = useState<{ open: boolean; device?: Device }>({ open: false })
+  const [registerDrawer, setRegisterDrawer] = useState(false)
+  const [notifOpen, setNotifOpen] = useState(false)
+
+  // Extra feedback modals
+  const [svcModal, setSvcModal] = useState<{ open: boolean; svc?: any }>({ open: false })
+  const [weightModal, setWeightModal] = useState<{ open: boolean; weight: number }>({ open: false, weight: 50 })
+  const [scheduleModal, setScheduleModal] = useState<boolean>(false)
+  const [apiAccessModal, setApiAccessModal] = useState<boolean>(false)
+  const [rbacModal, setRbacModal] = useState<{ open: boolean; user?: any }>({ open: false })
+
+  const [form] = Form.useForm()
+  const [productForm] = Form.useForm()
+  const [deviceForm] = Form.useForm()
+  const [cmdForm] = Form.useForm()
+
+  // Simulate telemetry stream, queue and device health
+  useInterval(() => {
+    const onlineDevices = devices.filter((d) => d.status === "online")
+    if (onlineDevices.length === 0) return
+    const randDevice = onlineDevices[Math.floor(Math.random() * onlineDevices.length)]
+    const v = Math.round(80 + Math.random() * 40 - 20)
+    const now = Date.now()
+    setTelemetry((prev) => {
+      const next = [...prev, { ts: now, value: v, deviceId: randDevice.id }].slice(-120)
+      return next
+    })
+    setTodayCount((c) => c + 1)
+    setBacklog((b) => Math.max(0, b + 1 - Math.floor(throughput / 100)))
+    setLogs((prev) => {
+      const product = products.find((p) => p.id === randDevice.productId)?.name ?? "-"
+      const next = [
+        {
+          key: `${now}`,
+          time: new Date(now).toLocaleTimeString(),
+          deviceId: randDevice.id,
+          product,
+          msg: `上报: value=${v}`,
+          level: "INFO" as const,
+        },
+        ...prev,
+      ].slice(0, 50)
+      return next
+    })
+    if (Math.random() < 0.03) {
+      setAlerts((a) => a + 1)
+      setLogs((prev) => [
+        {
+          key: `${now}-warn`,
+          time: new Date(now).toLocaleTimeString(),
+          deviceId: randDevice.id,
+          product: products.find((p) => p.id === randDevice.productId)?.name ?? "-",
+          msg: "告警: 压力异常",
+          level: "WARN",
+        },
+        ...prev,
+      ])
+    }
+    setDevices((prev) => prev.map((d) => (d.id === randDevice.id ? { ...d, lastSeen: new Date().toISOString() } : d)))
+  }, 1200)
+
+  const onlineCount = devices.filter((d) => d.status === "online").length
+
+  const menuItems: MenuProps["items"] = [
+    { key: "overview", icon: <Activity size={18} />, label: "总览" },
+    { key: "south", icon: <Server size={18} />, label: "南向接入" },
+    { key: "terminal", icon: <LinkIcon size={18} />, label: "终端接入" },
+    { key: "distribution", icon: <Send size={18} />, label: "数据分发" },
+    { key: "mq", icon: <Network size={18} />, label: "北向消息队列" },
+    { key: "warehouse", icon: <Database size={18} />, label: "数据仓库" },
+    { key: "openapi", icon: <Globe size={18} />, label: "数据共享开放" },
+    { key: "micro", icon: <Boxes size={18} />, label: "微服务" },
+    { key: "auth", icon: <ShieldCheck size={18} />, label: "认证鉴权" },
+  ]
+
+  const projectOptions = projects.map((p) => ({ label: p.name, value: p.id }))
+  const productOptions = products.map((p) => ({ label: p.name, value: p.id }))
+
+  function addProject(values: any) {
+    const p: Project = {
+      id: `p-${Math.random().toString(36).slice(2, 8)}`,
+      name: values.name,
+      description: values.description,
+      createdAt: new Date().toISOString(),
+    }
+    setProjects((prev) => [p, ...prev])
+    setProjectModalOpen(false)
+    form.resetFields()
+    globalMessage.success("项目已创建")
+  }
+  function addProduct(values: any) {
+    const prod: Product = {
+      id: `prod-${Math.random().toString(36).slice(2, 8)}`,
+      name: values.name,
+      protocol: values.protocol,
+      projectId: values.projectId,
+      thirdParty: values.thirdParty || false,
+      authToken: values.thirdParty ? `TP-${Math.random().toString(36).slice(2, 8)}` : undefined,
+    }
+    setProducts((prev) => [prod, ...prev])
+    setProductModalOpen(false)
+    productForm.resetFields()
+    globalMessage.success("产品已创建")
+  }
+  function addDevice(values: any) {
+    const dev: Device = {
+      id: values.id,
+      name: values.name,
+      productId: values.productId,
+      projectId: values.projectId,
+      status: "offline",
+      lat: values.lat ?? 31.23 + Math.random() * 0.05,
+      lng: values.lng ?? 121.47 + Math.random() * 0.05,
+    }
+    setDevices((prev) => [dev, ...prev])
+    setDeviceModalOpen(false)
+    deviceForm.resetFields()
+    globalMessage.success("设备已注册")
+  }
+  function toggleDevice(id: string, online: boolean) {
+    setDevices((prev) =>
+      prev.map((d) =>
+        d.id === id
+          ? { ...d, status: online ? "online" : "offline", lastSeen: online ? new Date().toISOString() : d.lastSeen }
+          : d,
+      ),
+    )
+    globalMessage.success(online ? "设备上线成功" : "设备已下线")
+  }
+  function sendCommand(values: any) {
+    const device = cmdModal.device
+    if (!device) return
+    const now = Date.now()
+    setLogs((prev) => [
+      {
+        key: `${now}-cmd`,
+        time: new Date(now).toLocaleTimeString(),
+        deviceId: device.id,
+        product: products.find((p) => p.id === device.productId)?.name ?? "-",
+        msg: `下发指令: ${values.serviceId} ${values.payload}`,
+        level: "INFO",
+      },
+      ...prev,
+    ])
+    setCmdModal({ open: false })
+    cmdForm.resetFields()
+    globalMessage.success("指令已发送")
+  }
+
+  const telemetrySeries = useMemo(() => {
+    const xs = telemetry.map((t) => new Date(t.ts).toLocaleTimeString())
+    const ys = telemetry.map((t) => t.value)
+    return { xs, ys }
+  }, [telemetry])
+
+  const alertLogs = logs.filter((l) => l.level !== "INFO").slice(0, 10)
+
+  return (
+    <Layout style={{ minHeight: "100vh" }}>
+      <Sider collapsible collapsed={collapsed} onCollapse={setCollapsed} width={240}>
+        <div style={{ height: 60, display: "flex", alignItems: "center", padding: "0 12px", gap: 8 }}>
+          <Sprout size={22} color="white" />
+          {!collapsed && <Text style={{ color: "white", fontWeight: 600 }}>生命线物联网平台</Text>}
+        </div>
+        <Menu
+          theme="dark"
+          mode="inline"
+          selectedKeys={[activeKey]}
+          items={menuItems}
+          onClick={(e) => setActiveKey(e.key)}
+        />
+      </Sider>
+      <Layout>
+        <Header
+          style={{
+            background: "white",
+            borderBottom: "1px solid #f0f0f0",
+            display: "flex",
+            alignItems: "center",
+            padding: "0 16px",
+            gap: 12,
+          }}
+        >
+          <Radar size={22} />
+          <Title level={4} style={{ margin: 0 }}>
+            城市生命线物联网平台
+          </Title>
+          <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 12 }}>
+            <Badge count={alerts} overflowCount={99}>
+              <Button icon={<Bell size={16} />} onClick={() => setNotifOpen(true)} aria-label="查看通知" />
+            </Badge>
+            <Button icon={<RefreshCw size={16} />} onClick={() => globalMessage.success("已刷新")} aria-label="刷新" />
+            <Button type="primary" icon={<CloudUpload size={16} />} onClick={() => setRegisterDrawer(true)}>
+              设备注册
+            </Button>
+          </div>
+        </Header>
+        <Content style={{ padding: 16 }}>
+          {activeKey === "overview" && (
+            <div className="space-y-4">
+              <Row gutter={[16, 16]}>
+                <Col xs={12} md={6}>
+                  <Card>
+                    <Statistic title="设备在线" value={onlineCount} prefix={<SignalHigh size={16} />} />
+                  </Card>
+                </Col>
+                <Col xs={12} md={6}>
+                  <Card>
+                    <Statistic title="今日上报" value={todayCount} prefix={<UploadIcon size={16} />} />
+                  </Card>
+                </Col>
+                <Col xs={12} md={6}>
+                  <Card>
+                    <Statistic title="队列积压" value={backlog} suffix="条" prefix={<Layers size={16} />} />
+                  </Card>
+                </Col>
+                <Col xs={12} md={6}>
+                  <Card>
+                    <Statistic title="告警数" value={alerts} prefix={<AlertIcon />} />
+                  </Card>
+                </Col>
+              </Row>
+              <Row gutter={[16, 16]}>
+                <Col xs={24} lg={16}>
+                  <Card title="实时监测趋势">
+                    <RealtimeLine title="压力/流量趋势" xs={telemetrySeries.xs} ys={telemetrySeries.ys} />
+                  </Card>
+                </Col>
+                <Col xs={24} lg={8}>
+                  <Card title="消息队列压力">
+                    <QueueGauge backlog={backlog} throughput={throughput} />
+                    <Divider />
+                    <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
+                      <Text>处理吞吐</Text>
+                      <Segmented
+                        options={[
+                          { label: "低", value: 80 },
+                          { label: "中", value: 120 },
+                          { label: "高", value: 200 },
+                        ]}
+                        value={throughput}
+                        onChange={(v) => {
+                          setThroughput(Number(v))
+                          globalMessage.success(`已切换吞吐:${v}/s`)
+                        }}
+                      />
+                    </div>
+                  </Card>
+                </Col>
+              </Row>
+              <Row gutter={[16, 16]}>
+                <Col xs={24} lg={12}>
+                  <Card title="设备分布地图">
+                    <div style={{ height: 360 }}>
+                      <DeviceMap devices={devices} />
+                    </div>
+                  </Card>
+                </Col>
+                <Col xs={24} lg={12}>
+                  <Card title="实时日志">
+                    <Table
+                      size="small"
+                      dataSource={logs}
+                      pagination={{ pageSize: 6 }}
+                      columns={[
+                        { title: "时间", dataIndex: "time", width: 100 },
+                        { title: "设备ID", dataIndex: "deviceId", width: 120 },
+                        { title: "产品", dataIndex: "product", width: 120 },
+                        {
+                          title: "级别",
+                          dataIndex: "level",
+                          width: 80,
+                          render: (lv) =>
+                            lv === "INFO" ? (
+                              <Tag color="blue">INFO</Tag>
+                            ) : lv === "WARN" ? (
+                              <Tag color="orange">WARN</Tag>
+                            ) : (
+                              <Tag color="red">ERROR</Tag>
+                            ),
+                        },
+                        { title: "消息", dataIndex: "msg" },
+                      ]}
+                    />
+                  </Card>
+                </Col>
+              </Row>
+            </div>
+          )}
+
+          {activeKey === "south" && (
+            <Card>
+              <Tabs
+                defaultActiveKey="projects"
+                items={[
+                  {
+                    key: "projects",
+                    label: (
+                      <div className="flex items-center gap-2">
+                        <Package size={16} /> 项目管理
+                      </div>
+                    ),
+                    children: (
+                      <>
+                        <div className="flex justify-between mb-3">
+                          <Text type="secondary">按项目分类设备,方便资产统一管理</Text>
+                          <Button type="primary" onClick={() => setProjectModalOpen(true)} icon={<PlusIcon />}>
+                            新建项目
+                          </Button>
+                        </div>
+                        <Table<Project>
+                          rowKey="id"
+                          dataSource={projects}
+                          columns={[
+                            { title: "项目名称", dataIndex: "name" },
+                            { title: "描述", dataIndex: "description" },
+                            { title: "创建时间", dataIndex: "createdAt", render: (t) => new Date(t).toLocaleString() },
+                            {
+                              title: "设备数",
+                              render: (_, r) => devices.filter((d) => d.projectId === r.id).length,
+                            },
+                          ]}
+                        />
+                        <Modal
+                          title="新建项目"
+                          open={projectModalOpen}
+                          onCancel={() => setProjectModalOpen(false)}
+                          onOk={() => form.submit()}
+                          okText="保存"
+                        >
+                          <Form form={form} layout="vertical" onFinish={addProject}>
+                            <Form.Item label="项目名称" name="name" rules={[{ required: true, message: "请输入名称" }]}>
+                              <Input placeholder="例如:城市燃气" />
+                            </Form.Item>
+                            <Form.Item label="描述" name="description">
+                              <Input placeholder="可选" />
+                            </Form.Item>
+                          </Form>
+                        </Modal>
+                      </>
+                    ),
+                  },
+                  {
+                    key: "products",
+                    label: (
+                      <div className="flex items-center gap-2">
+                        <Box size={16} /> 产品管理
+                      </div>
+                    ),
+                    children: (
+                      <>
+                        <div className="flex justify-between mb-3">
+                          <Text type="secondary">定义产品、添加设备,管理自建与第三方授权产品</Text>
+                          <Button type="primary" icon={<PlusIcon />} onClick={() => setProductModalOpen(true)}>
+                            新建产品
+                          </Button>
+                        </div>
+                        <Table<Product>
+                          rowKey="id"
+                          dataSource={products}
+                          columns={[
+                            { title: "名称", dataIndex: "name" },
+                            { title: "协议", dataIndex: "protocol" },
+                            {
+                              title: "归属项目",
+                              dataIndex: "projectId",
+                              render: (id) => projects.find((p) => p.id === id)?.name,
+                            },
+                            {
+                              title: "类型",
+                              dataIndex: "thirdParty",
+                              render: (v: boolean) =>
+                                v ? <Tag color="purple">第三方</Tag> : <Tag color="green">自建</Tag>,
+                            },
+                            {
+                              title: "授权Token",
+                              dataIndex: "authToken",
+                              render: (t) => (t ? <Text code>{t}</Text> : <Text type="secondary">-</Text>),
+                            },
+                            {
+                              title: "设备数",
+                              render: (_, r) => devices.filter((d) => d.productId === r.id).length,
+                            },
+                          ]}
+                        />
+                        <Modal
+                          title="新建产品"
+                          open={productModalOpen}
+                          onCancel={() => setProductModalOpen(false)}
+                          onOk={() => productForm.submit()}
+                          okText="保存"
+                        >
+                          <Form form={productForm} layout="vertical" onFinish={addProduct}>
+                            <Form.Item label="产品名称" name="name" rules={[{ required: true }]}>
+                              <Input placeholder="例如:压力传感器A" />
+                            </Form.Item>
+                            <Form.Item label="接入协议" name="protocol" rules={[{ required: true }]}>
+                              <Select
+                                options={[
+                                  { label: "TCP", value: "TCP" },
+                                  { label: "UDP", value: "UDP" },
+                                  { label: "LWM2M", value: "LWM2M" },
+                                  { label: "MQTTS", value: "MQTTS" },
+                                  { label: "HTTP/HTTPS", value: "HTTP/HTTPS" },
+                                ]}
+                              />
+                            </Form.Item>
+                            <Form.Item label="归属项目" name="projectId" rules={[{ required: true }]}>
+                              <Select options={projectOptions} />
+                            </Form.Item>
+                            <Form.Item label="第三方产品" name="thirdParty" valuePropName="checked">
+                              <Switch />
+                            </Form.Item>
+                          </Form>
+                        </Modal>
+                      </>
+                    ),
+                  },
+                  {
+                    key: "thing",
+                    label: (
+                      <div className="flex items-center gap-2">
+                        <FileJson2 size={16} /> 物模型管理
+                      </div>
+                    ),
+                    children: (
+                      <>
+                        <Row gutter={[16, 16]}>
+                          <Col xs={24} md={12}>
+                            <Card
+                              title="定义物模型(JSON)"
+                              extra={
+                                <Button
+                                  onClick={() => {
+                                    const t: ThingModel = {
+                                      id: `tm-${Math.random().toString(36).slice(2, 8)}`,
+                                      productId: products[0]?.id ?? "",
+                                      json: JSON.stringify(
+                                        {
+                                          properties: [{ id: "temp", name: "温度", type: "number", unit: "℃" }],
+                                          events: [],
+                                          services: [],
+                                        },
+                                        null,
+                                        2,
+                                      ),
+                                    }
+                                    setThingModels((prev) => [t, ...prev])
+                                    globalMessage.success("已创建示例物模型")
+                                  }}
+                                >
+                                  创建示例
+                                </Button>
+                              }
+                            >
+                              <Table
+                                size="small"
+                                rowKey="id"
+                                dataSource={thingModels}
+                                pagination={{ pageSize: 5 }}
+                                columns={[
+                                  { title: "ID", dataIndex: "id", width: 120 },
+                                  {
+                                    title: "产品",
+                                    dataIndex: "productId",
+                                    render: (id: string) => products.find((p) => p.id === id)?.name ?? "-",
+                                  },
+                                  {
+                                    title: "属性数",
+                                    render: (_, r: ThingModel) => {
+                                      try {
+                                        const o = JSON.parse(r.json)
+                                        return o.properties?.length ?? 0
+                                      } catch {
+                                        return "-"
+                                      }
+                                    },
+                                  },
+                                  {
+                                    title: "操作",
+                                    render: (_, r: ThingModel) => (
+                                      <Space>
+                                        <Popover
+                                          title="物模型预览"
+                                          content={
+                                            <pre style={{ maxWidth: 360, maxHeight: 200, overflow: "auto", margin: 0 }}>
+                                              {r.json}
+                                            </pre>
+                                          }
+                                        >
+                                          <Button size="small">预览</Button>
+                                        </Popover>
+                                        <Button
+                                          size="small"
+                                          onClick={() => {
+                                            const prod = products.find((p) => p.id === r.productId)
+                                            globalMessage.success(`已应用到产品 ${prod?.name ?? "-"}`)
+                                          }}
+                                        >
+                                          应用到产品
+                                        </Button>
+                                      </Space>
+                                    ),
+                                  },
+                                ]}
+                              />
+                            </Card>
+                          </Col>
+                          <Col xs={24} md={12}>
+                            <Card title="快速校验">
+                              <Form
+                                layout="vertical"
+                                onFinish={(v) => {
+                                  try {
+                                    const obj = JSON.parse(v.json)
+                                    if (!Array.isArray(obj.properties)) throw new Error("缺少 properties")
+                                    globalMessage.success("物模型JSON校验通过")
+                                  } catch (e: any) {
+                                    globalMessage.error(`JSON错误: ${e.message}`)
+                                  }
+                                }}
+                              >
+                                <Form.Item
+                                  label="JSON定义"
+                                  name="json"
+                                  rules={[{ required: true, message: "请输入物模型JSON" }]}
+                                  initialValue={thingModels[0]?.json}
+                                >
+                                  <TextArea rows={12} placeholder="输入物模型JSON" />
+                                </Form.Item>
+                                <Button type="primary" htmlType="submit" icon={<Check size={16} />}>
+                                  校验
+                                </Button>
+                              </Form>
+                            </Card>
+                          </Col>
+                        </Row>
+                      </>
+                    ),
+                  },
+                  {
+                    key: "devices",
+                    label: (
+                      <div className="flex items-center gap-2">
+                        <Factory size={16} /> 设备管理
+                      </div>
+                    ),
+                    children: (
+                      <>
+                        <div className="flex justify-between mb-3">
+                          <Text type="secondary">注册、上线、上报数据与指令下发</Text>
+                          <Space>
+                            <Button icon={<PlusIcon />} onClick={() => setDeviceModalOpen(true)}>
+                              注册设备
+                            </Button>
+                            <Button type="primary" icon={<Send size={16} />} onClick={() => setRegisterDrawer(true)}>
+                              批量/页面注册
+                            </Button>
+                          </Space>
+                        </div>
+                        <Table<Device>
+                          rowKey="id"
+                          dataSource={devices}
+                          pagination={{ pageSize: 8 }}
+                          columns={[
+                            { title: "设备ID", dataIndex: "id", width: 140 },
+                            { title: "名称", dataIndex: "name" },
+                            {
+                              title: "项目",
+                              dataIndex: "projectId",
+                              render: (id) => projects.find((p) => p.id === id)?.name,
+                            },
+                            {
+                              title: "产品",
+                              dataIndex: "productId",
+                              render: (id) => products.find((p) => p.id === id)?.name,
+                            },
+                            {
+                              title: "状态",
+                              dataIndex: "status",
+                              render: (s: Device["status"]) =>
+                                s === "online" ? <Tag color="green">在线</Tag> : <Tag>离线</Tag>,
+                            },
+                            {
+                              title: "最后上报",
+                              dataIndex: "lastSeen",
+                              render: (t) => (t ? new Date(t).toLocaleString() : "-"),
+                            },
+                            {
+                              title: "操作",
+                              render: (_, r) => (
+                                <Space>
+                                  {r.status === "online" ? (
+                                    <Button size="small" onClick={() => toggleDevice(r.id, false)}>
+                                      下线
+                                    </Button>
+                                  ) : (
+                                    <Button size="small" type="primary" onClick={() => toggleDevice(r.id, true)}>
+                                      上线
+                                    </Button>
+                                  )}
+                                  <Button
+                                    size="small"
+                                    icon={<Send size={14} />}
+                                    onClick={() => setCmdModal({ open: true, device: r })}
+                                  >
+                                    下发指令
+                                  </Button>
+                                  <Button size="small" onClick={() => setDataDrawer({ open: true, device: r })}>
+                                    上报数据
+                                  </Button>
+                                </Space>
+                              ),
+                            },
+                          ]}
+                        />
+                        <Modal
+                          title="注册设备"
+                          open={deviceModalOpen}
+                          onCancel={() => setDeviceModalOpen(false)}
+                          onOk={() => deviceForm.submit()}
+                          okText="保存"
+                        >
+                          <Form form={deviceForm} layout="vertical" onFinish={addDevice}>
+                            <Form.Item label="设备ID" name="id" rules={[{ required: true }]}>
+                              <Input placeholder="例如:dev-004" />
+                            </Form.Item>
+                            <Form.Item label="设备名称" name="name" rules={[{ required: true }]}>
+                              <Input placeholder="例如:压力A-004" />
+                            </Form.Item>
+                            <Form.Item label="项目" name="projectId" rules={[{ required: true }]}>
+                              <Select options={projectOptions} />
+                            </Form.Item>
+                            <Form.Item label="产品" name="productId" rules={[{ required: true }]}>
+                              <Select options={productOptions} />
+                            </Form.Item>
+                            <Row gutter={8}>
+                              <Col span={12}>
+                                <Form.Item label="纬度" name="lat">
+                                  <Input type="number" placeholder="31.23" />
+                                </Form.Item>
+                              </Col>
+                              <Col span={12}>
+                                <Form.Item label="经度" name="lng">
+                                  <Input type="number" placeholder="121.47" />
+                                </Form.Item>
+                              </Col>
+                            </Row>
+                          </Form>
+                        </Modal>
+
+                        <Modal
+                          title={`下发指令 - ${cmdModal.device?.name ?? ""}`}
+                          open={cmdModal.open}
+                          onCancel={() => setCmdModal({ open: false })}
+                          onOk={() => cmdForm.submit()}
+                          okText="发送"
+                        >
+                          <Form form={cmdForm} layout="vertical" onFinish={sendCommand}>
+                            <Form.Item label="服务标识" name="serviceId" rules={[{ required: true }]}>
+                              <Input placeholder="例如:reset" />
+                            </Form.Item>
+                            <Form.Item label="负载(JSON)" name="payload" rules={[{ required: true }]}>
+                              <TextArea rows={6} placeholder='例如:{"delay":5}' />
+                            </Form.Item>
+                          </Form>
+                        </Modal>
+
+                        <Drawer
+                          title={`上报数据 - ${dataDrawer.device?.name ?? ""}`}
+                          open={dataDrawer.open}
+                          onClose={() => setDataDrawer({ open: false })}
+                          width={520}
+                        >
+                          <Table
+                            size="small"
+                            pagination={{ pageSize: 10 }}
+                            dataSource={logs.filter((l) => l.deviceId === dataDrawer.device?.id)}
+                            columns={[
+                              { title: "时间", dataIndex: "time", width: 120 },
+                              { title: "级别", dataIndex: "level", width: 80 },
+                              { title: "消息", dataIndex: "msg" },
+                            ]}
+                          />
+                        </Drawer>
+                      </>
+                    ),
+                  },
+                ]}
+              />
+            </Card>
+          )}
+
+          {activeKey === "terminal" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={14}>
+                <Card title="设备连接与认证流程">
+                  <Steps
+                    direction="vertical"
+                    items={[
+                      {
+                        title: "选择协议连接",
+                        description: "支持 TCP / UDP / LWM2M / MQTTS / HTTP(S)",
+                        icon: <LinkIcon size={16} />,
+                      },
+                      {
+                        title: "设备注册(批量/单一)",
+                        description: "提供服务交互或页面交互两种方式",
+                        icon: <Users size={16} />,
+                      },
+                      {
+                        title: "授权与安全认证",
+                        description: "加解密与签名认证模块,确保高安全场景",
+                        icon: <Lock size={16} />,
+                      },
+                      {
+                        title: "上线与心跳",
+                        description: "接入后定期保活,保持在线状态",
+                        icon: <CheckCircle2 size={16} />,
+                      },
+                    ]}
+                  />
+                  <Divider />
+                  <Form
+                    layout="vertical"
+                    onFinish={() => {
+                      globalMessage.success("认证通过")
+                    }}
+                  >
+                    <Row gutter={12}>
+                      <Col span={12}>
+                        <Form.Item label="设备ID" name="id" rules={[{ required: true }]}>
+                          <Input placeholder="dev-xxx" />
+                        </Form.Item>
+                      </Col>
+                      <Col span={12}>
+                        <Form.Item label="协议" name="protocol" rules={[{ required: true }]}>
+                          <Select
+                            options={[
+                              { label: "TCP", value: "TCP" },
+                              { label: "UDP", value: "UDP" },
+                              { label: "LWM2M", value: "LWM2M" },
+                              { label: "MQTTS", value: "MQTTS" },
+                              { label: "HTTP/HTTPS", value: "HTTP/HTTPS" },
+                            ]}
+                          />
+                        </Form.Item>
+                      </Col>
+                    </Row>
+                    <Form.Item label="设备密钥" name="secret" rules={[{ required: true }]}>
+                      <Input.Password placeholder="******" />
+                    </Form.Item>
+                    <Button type="primary" htmlType="submit" icon={<ShieldCheck size={16} />}>
+                      认证
+                    </Button>
+                  </Form>
+                </Card>
+              </Col>
+              <Col xs={24} lg={10}>
+                <Card title="批量注册(CSV)">
+                  <Upload.Dragger
+                    multiple
+                    beforeUpload={() => {
+                      globalMessage.success("CSV已接收,已加入处理队列")
+                      return false
+                    }}
+                  >
+                    <p className="ant-upload-drag-icon">
+                      <UploadIcon />
+                    </p>
+                    <p className="ant-upload-text">点击或拖拽CSV文件到此处上传</p>
+                    <p className="ant-upload-hint">示例列:deviceId,productId,projectId,secret</p>
+                  </Upload.Dragger>
+                </Card>
+                <Card title="连接参数示例" className="mt-4">
+                  <Descriptions size="small" column={1} bordered>
+                    <Descriptions.Item label="MQTTS 服务器">mqtts://iot.example.com:8883</Descriptions.Item>
+                    <Descriptions.Item label="HTTP 上报">POST https://api.example.com/iot/ingest</Descriptions.Item>
+                    <Descriptions.Item label="LWM2M 服务器">coaps://lwm2m.example.com:5684</Descriptions.Item>
+                  </Descriptions>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "distribution" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} md={12}>
+                <Card title="数据接入与加工流程">
+                  <Timeline
+                    items={[
+                      {
+                        color: "blue",
+                        children: "协议接入(TCP/UDP/LWM2M/MQTTS/HTTP)",
+                      },
+                      {
+                        color: "green",
+                        children: "报文解析(TLV/JSON/自定义)",
+                      },
+                      {
+                        color: "green",
+                        children: "清洗与标准化(字段映射/单位换算/去噪)",
+                      },
+                      {
+                        color: "green",
+                        children: "入库(时序/对象/冷存)",
+                      },
+                      {
+                        color: "purple",
+                        children: "开放共享(发布/订阅)",
+                      },
+                    ]}
+                  />
+                </Card>
+                <Card title="实时发布/订阅" className="mt-4">
+                  <Space direction="vertical" style={{ width: "100%" }}>
+                    <div className="flex items-center justify-between">
+                      <Text>发布主题</Text>
+                      <Text code>{`/city/${products[0]?.id ?? "prod"}/pressure`}</Text>
+                    </div>
+                    <div className="flex items-center gap-8">
+                      <div className="flex items-center gap-2">
+                        <Text type="secondary">发布</Text>
+                        <Switch defaultChecked onChange={(c) => globalMessage.success(c ? "已开启发布" : "已关闭发布")} />
+                      </div>
+                      <div className="flex items-center gap-2">
+                        <Text type="secondary">订阅</Text>
+                        <Switch defaultChecked onChange={(c) => globalMessage.success(c ? "已开启订阅" : "已关闭订阅")} />
+                      </div>
+                      <Button icon={<Send size={16} />} onClick={() => globalMessage.success("已发布一条消息")}>
+                        推送一条
+                      </Button>
+                    </div>
+                  </Space>
+                </Card>
+              </Col>
+              <Col xs={24} md={12}>
+                <Card title="加工结果预览">
+                  <pre
+                    style={{
+                      maxHeight: 320,
+                      overflow: "auto",
+                      background: "#0b1020",
+                      color: "#d6e5ff",
+                      padding: 12,
+                      borderRadius: 8,
+                    }}
+                  >
+                    {`{
+  "deviceId": "${devices[0]?.id ?? "dev"}",
+  "ts": "${new Date().toISOString()}",
+  "model": "pressure",
+  "value": ${Number.isFinite(telemetrySeries.ys.at(-1) ?? Number.NaN) ? telemetrySeries.ys.at(-1) : 100},
+  "unit": "kPa",
+  "quality": "good"
+}`}
+                  </pre>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "mq" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={10}>
+                <Card title="流量削峰与高并发处理">
+                  <QueueGauge backlog={backlog} throughput={throughput} />
+                  <Divider />
+                  <Text>解耦采集与处理服务,实现高并发接入</Text>
+                  <Progress className="mt-3" percent={Math.min(100, Math.round((throughput / 250) * 100))} steps={12} />
+                </Card>
+                <Card title="指标类型转换" className="mt-4">
+                  <Form
+                    layout="vertical"
+                    onFinish={() => globalMessage.success("转换规则已保存")}
+                    initialValues={{ topic: "按产品", format: "物模型", compress: false }}
+                  >
+                    <Form.Item label="主题维度" name="topic">
+                      <Select
+                        options={[
+                          { label: "按产品", value: "按产品" },
+                          { label: "按物模型指标", value: "按物模型指标" },
+                          { label: "按项目", value: "按项目" },
+                        ]}
+                      />
+                    </Form.Item>
+                    <Form.Item label="格式转换" name="format">
+                      <Select
+                        options={[
+                          { label: "物模型(标准)", value: "物模型" },
+                          { label: "CSV", value: "CSV" },
+                          { label: "自定义", value: "自定义" },
+                        ]}
+                      />
+                    </Form.Item>
+                    <Form.Item label="开启压缩" name="compress" valuePropName="checked">
+                      <Switch />
+                    </Form.Item>
+                    <Button type="primary" htmlType="submit" icon={<Check size={16} />}>
+                      保存
+                    </Button>
+                  </Form>
+                </Card>
+              </Col>
+              <Col xs={24} lg={14}>
+                <Card title="转换前/后对比">
+                  <Row gutter={12}>
+                    <Col span={12}>
+                      <Title level={5}>原始数据</Title>
+                      <pre
+                        style={{
+                          background: "#0b1020",
+                          color: "#d6e5ff",
+                          padding: 12,
+                          borderRadius: 8,
+                          minHeight: 220,
+                        }}
+                      >
+                        {`{"dev":"${devices[0]?.id ?? "dev"}","p":${telemetrySeries.ys.at(-1) ?? 100},"ts":${Date.now()}}`}
+                      </pre>
+                    </Col>
+                    <Col span={12}>
+                      <Title level={5}>标准化后</Title>
+                      <pre
+                        style={{
+                          background: "#0b1020",
+                          color: "#d6e5ff",
+                          padding: 12,
+                          borderRadius: 8,
+                          minHeight: 220,
+                        }}
+                      >
+                        {`{
+  "deviceId": "${devices[0]?.id ?? "dev"}",
+  "metrics": [{"id":"pressure","value": ${telemetrySeries.ys.at(-1) ?? 100}, "unit":"kPa"}],
+  "timestamp": "${new Date().toISOString()}"
+}`}
+                      </pre>
+                    </Col>
+                  </Row>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "warehouse" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={14}>
+                <Card title="原始数据(近50条)">
+                  <Table
+                    size="small"
+                    dataSource={logs.map((l, i) => ({ ...l, idx: i }))}
+                    pagination={{ pageSize: 8 }}
+                    columns={[
+                      { title: "#", dataIndex: "idx", width: 60 },
+                      { title: "时间", dataIndex: "time", width: 120 },
+                      { title: "设备ID", dataIndex: "deviceId", width: 140 },
+                      { title: "消息", dataIndex: "msg" },
+                    ]}
+                  />
+                </Card>
+              </Col>
+              <Col xs={24} lg={10}>
+                <Card title="ETL 作业">
+                  <Collapse
+                    items={[
+                      {
+                        key: "extract",
+                        label: "抽取",
+                        children: <Text>从接入缓冲区读取原始数据,写入ODS层。</Text>,
+                      },
+                      {
+                        key: "clean",
+                        label: "清洗",
+                        children: <Text>字段校验、缺失填充、单位换算、异常剔除。</Text>,
+                      },
+                      { key: "load", label: "加载", children: <Text>入库到时序与事实表,供分析与API服务。</Text> },
+                    ]}
+                    defaultActiveKey={["extract", "clean", "load"]}
+                  />
+                  <Divider />
+                  <Space>
+                    <Button type="primary" icon={<PlayIcon />} onClick={() => globalMessage.success("已触发 ETL 作业")}>
+                      运行一次
+                    </Button>
+                    <Button icon={<Settings size={16} />} onClick={() => setScheduleModal(true)}>
+                      计划配置
+                    </Button>
+                  </Space>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "openapi" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={12}>
+                <Card title="接口服务管理">
+                  <Table
+                    size="small"
+                    rowKey="name"
+                    dataSource={[
+                      { name: "实时监测查询", path: "/api/metrics/realtime", status: "running" },
+                      { name: "设备状态查询", path: "/api/devices/status", status: "running" },
+                      { name: "历史数据查询", path: "/api/metrics/history", status: "stopped" },
+                    ]}
+                    columns={[
+                      { title: "名称", dataIndex: "name" },
+                      { title: "路径", dataIndex: "path", render: (p) => <Text code>{p}</Text> },
+                      {
+                        title: "状态",
+                        dataIndex: "status",
+                        render: (s) =>
+                          s === "running" ? (
+                            <Badge status="success" text="运行中" />
+                          ) : (
+                            <Badge status="default" text="已停止" />
+                          ),
+                      },
+                      {
+                        title: "操作",
+                        render: (_, r) => (
+                          <Space>
+                            <Button size="small" onClick={() => setSvcModal({ open: true, svc: r })}>
+                              配置
+                            </Button>
+                            <Button
+                              size="small"
+                              type="primary"
+                              onClick={() => globalMessage.success(r.status === "running" ? "已停止" : "已启动")}
+                            >
+                              {r.status === "running" ? "停止" : "启动"}
+                            </Button>
+                          </Space>
+                        ),
+                      },
+                    ]}
+                  />
+                </Card>
+              </Col>
+              <Col xs={24} lg={12}>
+                <Card title="接口定义示例">
+                  <pre
+                    style={{ background: "#0b1020", color: "#d6e5ff", padding: 12, borderRadius: 8, minHeight: 240 }}
+                  >
+                    {`GET /api/metrics/realtime?productId=prod1
+
+Response
+{
+  "code": 0,
+  "data": [
+    {"deviceId":"dev-001","metric":"pressure","value": 112.3,"unit":"kPa","ts":"${new Date().toISOString()}"}
+  ]
+}`}
+                  </pre>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "micro" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={12}>
+                <Card title="注册中心(服务发现/健康检查/负载均衡)">
+                  <Table
+                    size="small"
+                    rowKey="name"
+                    dataSource={[
+                      {
+                        name: "ingest-service",
+                        addr: "10.0.0.2:7001",
+                        status: Math.random() > 0.05 ? "UP" : "DOWN",
+                        load: "32%",
+                      },
+                      { name: "parser-service", addr: "10.0.0.3:7002", status: "UP", load: "41%" },
+                      { name: "mq-gateway", addr: "10.0.0.4:7003", status: "UP", load: "27%" },
+                      { name: "api-service", addr: "10.0.0.5:7004", status: "UP", load: "36%" },
+                    ]}
+                    columns={[
+                      { title: "服务", dataIndex: "name" },
+                      { title: "地址", dataIndex: "addr" },
+                      {
+                        title: "状态",
+                        dataIndex: "status",
+                        render: (s) =>
+                          s === "UP" ? <Badge status="success" text="UP" /> : <Badge status="error" text="DOWN" />,
+                      },
+                      { title: "负载", dataIndex: "load" },
+                      {
+                        title: "操作",
+                        render: (_, r) => (
+                          <Space>
+                            <Button
+                              size="small"
+                              icon={<RefreshCw size={14} />}
+                              onClick={() => globalMessage.success(`已触发健康检查:${r.name}`)}
+                            >
+                              健康检查
+                            </Button>
+                            <Button size="small" onClick={() => setWeightModal({ open: true, weight: 50 })}>
+                              权重
+                            </Button>
+                          </Space>
+                        ),
+                      },
+                    ]}
+                  />
+                </Card>
+              </Col>
+              <Col xs={24} lg={12}>
+                <Card title="配置中心(动态配置/多环境)">
+                  <Form
+                    layout="vertical"
+                    onFinish={() => globalMessage.success("配置已更新")}
+                    initialValues={{ env: "dev", log: "info", db: "postgres://neon-db/..." }}
+                  >
+                    <Form.Item label="环境" name="env">
+                      <Select
+                        options={[
+                          { label: "开发", value: "dev" },
+                          { label: "测试", value: "test" },
+                          { label: "生产", value: "prod" },
+                        ]}
+                      />
+                    </Form.Item>
+                    <Form.Item label="日志级别" name="log">
+                      <Select
+                        options={[
+                          { label: "debug", value: "debug" },
+                          { label: "info", value: "info" },
+                          { label: "warn", value: "warn" },
+                          { label: "error", value: "error" },
+                        ]}
+                      />
+                    </Form.Item>
+                    <Form.Item label="数据库连接串" name="db">
+                      <Input placeholder="postgres://..." />
+                    </Form.Item>
+                    <Space>
+                      <Button type="primary" htmlType="submit" icon={<Check size={16} />}>
+                        保存并推送
+                      </Button>
+                      <Button icon={<Cog size={16} />} onClick={() => setApiAccessModal(true)}>
+                        访问接口
+                      </Button>
+                    </Space>
+                  </Form>
+                </Card>
+              </Col>
+            </Row>
+          )}
+
+          {activeKey === "auth" && (
+            <Row gutter={[16, 16]}>
+              <Col xs={24} lg={12}>
+                <Card title="OAuth 2.0">
+                  <Form
+                    layout="vertical"
+                    onFinish={() => globalMessage.success("已签发示例Token")}
+                    initialValues={{ clientId: "demo-client", scope: "read:devices", grant: "client_credentials" }}
+                  >
+                    <Form.Item label="Client ID" name="clientId" rules={[{ required: true }]}>
+                      <Input />
+                    </Form.Item>
+                    <Form.Item label="授权范围" name="scope">
+                      <Input placeholder="read:devices write:commands" />
+                    </Form.Item>
+                    <Form.Item label="授权模式" name="grant">
+                      <Select
+                        options={[
+                          { label: "Client Credentials", value: "client_credentials" },
+                          { label: "Authorization Code", value: "authorization_code" },
+                        ]}
+                      />
+                    </Form.Item>
+                    <Button type="primary" htmlType="submit" icon={<ShieldCheck size={16} />}>
+                      获取Token
+                    </Button>
+                  </Form>
+                  <Divider />
+                  <Text>示例Token</Text>
+                  <pre style={{ background: "#0b1020", color: "#d6e5ff", padding: 12, borderRadius: 8 }}>
+                    {"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.demo.payload.signature"}
+                  </pre>
+                </Card>
+              </Col>
+              <Col xs={24} lg={12}>
+                <Card title="RBAC 权限管理">
+                  <Row gutter={12}>
+                    <Col span={12}>
+                      <Title level={5}>角色</Title>
+                      <Table
+                        size="small"
+                        pagination={false}
+                        dataSource={[
+                          { key: "r1", name: "管理员", perms: "ALL" },
+                          { key: "r2", name: "运维", perms: "devices:read, commands:write" },
+                          { key: "r3", name: "访客", perms: "devices:read" },
+                        ]}
+                        columns={[
+                          { title: "角色", dataIndex: "name" },
+                          { title: "权限", dataIndex: "perms" },
+                        ]}
+                      />
+                    </Col>
+                    <Col span={12}>
+                      <Title level={5}>用户</Title>
+                      <Table
+                        size="small"
+                        pagination={false}
+                        dataSource={[
+                          { key: "u1", name: "alice", role: "管理员" },
+                          { key: "u2", name: "bob", role: "运维" },
+                        ]}
+                        columns={[
+                          { title: "用户", dataIndex: "name" },
+                          { title: "角色", dataIndex: "role" },
+                          {
+                            title: "操作",
+                            render: (_, r) => (
+                              <Button size="small" onClick={() => setRbacModal({ open: true, user: r })}>
+                                分配权限
+                              </Button>
+                            ),
+                          },
+                        ]}
+                      />
+                    </Col>
+                  </Row>
+                </Card>
+              </Col>
+            </Row>
+          )}
+        </Content>
+      </Layout>
+
+      {/* Drawer: 设备注册引导 */}
+      <Drawer title="设备注册与授权" open={registerDrawer} onClose={() => setRegisterDrawer(false)} width={640}>
+        <Steps
+          current={1}
+          items={[{ title: "选择产品" }, { title: "注册设备" }, { title: "获取密钥" }, { title: "接入测试" }]}
+        />
+        <Divider />
+        <Form
+          layout="vertical"
+          onFinish={(v) => {
+            addDevice({
+              id: v.id,
+              name: v.name,
+              projectId: products.find((p) => p.id === v.productId)?.projectId ?? projects[0]?.id,
+              productId: v.productId,
+              lat: 31.23 + Math.random() * 0.05,
+              lng: 121.47 + Math.random() * 0.05,
+            })
+            globalMessage.success("注册成功")
+          }}
+        >
+          <Form.Item label="产品" name="productId" rules={[{ required: true }]}>
+            <Select options={productOptions} />
+          </Form.Item>
+          <Row gutter={12}>
+            <Col span={12}>
+              <Form.Item label="设备ID" name="id" rules={[{ required: true }]}>
+                <Input placeholder="dev-xxx" />
+              </Form.Item>
+            </Col>
+            <Col span={12}>
+              <Form.Item label="设备名称" name="name" rules={[{ required: true }]}>
+                <Input />
+              </Form.Item>
+            </Col>
+          </Row>
+          <Form.Item>
+            <Space>
+              <Button htmlType="submit" type="primary" icon={<Check size={16} />}>
+                注册
+              </Button>
+              <Button icon={<UploadIcon size={16} />} onClick={() => globalMessage.success("已接收CSV")}>
+                导入CSV
+              </Button>
+            </Space>
+          </Form.Item>
+        </Form>
+        <Divider />
+        <Title level={5}>连接示例(MQTTS)</Title>
+        <pre style={{ background: "#0b1020", color: "#d6e5ff", padding: 12, borderRadius: 8 }}>
+          {`mqtts://iot.example.com:8883
+username: <deviceId>
+password: <deviceSecret>
+topic: /up/<product>/<deviceId>`}
+        </pre>
+      </Drawer>
+
+      {/* Notifications modal */}
+      <Modal title="通知中心" open={notifOpen} onCancel={() => setNotifOpen(false)} onOk={() => setNotifOpen(false)}>
+        {alertLogs.length === 0 ? (
+          <Text type="secondary">暂无告警</Text>
+        ) : (
+          <Table
+            size="small"
+            pagination={false}
+            dataSource={alertLogs}
+            columns={[
+              { title: "时间", dataIndex: "time", width: 120 },
+              { title: "设备ID", dataIndex: "deviceId", width: 140 },
+              { title: "消息", dataIndex: "msg" },
+            ]}
+          />
+        )}
+      </Modal>
+
+      {/* OpenAPI config modal */}
+      <Modal
+        title={`服务配置 - ${svcModal.svc?.name ?? ""}`}
+        open={svcModal.open}
+        onCancel={() => setSvcModal({ open: false })}
+        onOk={() => {
+          setSvcModal({ open: false })
+          globalMessage.success("配置已保存")
+        }}
+      >
+        <Form layout="vertical" initialValues={svcModal.svc}>
+          <Form.Item label="名称" name="name">
+            <Input disabled />
+          </Form.Item>
+          <Form.Item label="路径" name="path">
+            <Input />
+          </Form.Item>
+          <Form.Item label="限流(req/s)" name="qps">
+            <Input placeholder="例如 200" />
+          </Form.Item>
+        </Form>
+      </Modal>
+
+      {/* Service weight modal */}
+      <Modal
+        title="实例权重"
+        open={weightModal.open}
+        onCancel={() => setWeightModal((s) => ({ ...s, open: false }))}
+        onOk={() => {
+          setWeightModal((s) => ({ ...s, open: false }))
+          globalMessage.success(`已设置权重:${weightModal.weight}`)
+        }}
+      >
+        <Slider
+          min={0}
+          max={100}
+          value={weightModal.weight}
+          onChange={(v) => setWeightModal({ open: true, weight: Number(v) })}
+        />
+      </Modal>
+
+      {/* Warehouse schedule modal */}
+      <Modal
+        title="ETL 计划配置"
+        open={scheduleModal}
+        onCancel={() => setScheduleModal(false)}
+        onOk={() => {
+          setScheduleModal(false)
+          globalMessage.success("计划已保存")
+        }}
+      >
+        <Form layout="vertical" initialValues={{ cron: "0 */1 * * *", retry: 3 }}>
+          <Form.Item label="Cron 表达式" name="cron">
+            <Input />
+          </Form.Item>
+          <Form.Item label="失败重试次数" name="retry">
+            <Input type="number" />
+          </Form.Item>
+        </Form>
+      </Modal>
+
+      {/* Config center API access modal */}
+      <Modal
+        title="配置访问接口"
+        open={apiAccessModal}
+        onCancel={() => setApiAccessModal(false)}
+        onOk={() => setApiAccessModal(false)}
+      >
+        <Descriptions size="small" column={1} bordered>
+          <Descriptions.Item label="GET">
+            <Text code>/config/{`{env}`}</Text>
+          </Descriptions.Item>
+          <Descriptions.Item label="POST 推送更新">
+            <Text code>/config/push</Text>
+          </Descriptions.Item>
+        </Descriptions>
+      </Modal>
+
+      {/* RBAC assign modal */}
+      <Modal
+        title={`分配权限 - ${rbacModal.user?.name ?? ""}`}
+        open={rbacModal.open}
+        onCancel={() => setRbacModal({ open: false })}
+        onOk={() => {
+          setRbacModal({ open: false })
+          globalMessage.success("权限已更新")
+        }}
+      >
+        <Form layout="vertical" initialValues={{ role: rbacModal.user?.role ?? "访客" }}>
+          <Form.Item label="角色" name="role">
+            <Select
+              options={[
+                { label: "管理员", value: "管理员" },
+                { label: "运维", value: "运维" },
+                { label: "访客", value: "访客" },
+              ]}
+            />
+          </Form.Item>
+        </Form>
+      </Modal>
+    </Layout>
+  )
+}
+
+function PlusIcon() {
+  return (
+    <span aria-hidden className="inline-flex">
+      <svg width="0" height="0" />
+    </span>
+  )
+}
+function AlertIcon() {
+  return (
+    <span aria-hidden className="inline-flex">
+      <svg width="0" height="0" />
+    </span>
+  )
+}
+function PlayIcon() {
+  return (
+    <span aria-hidden className="inline-flex">
+      <svg width="0" height="0" />
+    </span>
+  )
+}