diff --git a/src/api/park.ts b/src/api/park.ts index e670614..834a7a9 100644 --- a/src/api/park.ts +++ b/src/api/park.ts @@ -7,6 +7,7 @@ import type { ParkCompany, ScreenData, Tenant, + ParkScreen, } from "@/types"; /** 园区端(/park/api/*,server-core /park 前缀子应用,多租户按 tenant_id 隔离)。 */ @@ -44,4 +45,10 @@ export const parkApi = { screenData: (tenantId: string) => api.get("/park/api/screen/data", { tenant_id: tenantId }), updateScreenData: (tenantId: string, body: Partial) => api.put(`/park/api/screen/data?tenant_id=${tenantId}`, body), + + // ---- 屏幕管理(园区端创建/管理大屏设备) ---- + screens: (tenantId: string) => api.get("/park/api/screens", { tenant_id: tenantId }), + createScreen: (tenantId: string, body: Partial) => + api.post(`/park/api/screens?tenant_id=${tenantId}`, body), + deleteScreen: (tenantId: string, id: string) => api.del<{ ok: boolean }>(`/park/api/screens/${id}?tenant_id=${tenantId}`), }; diff --git a/src/pages/park/index.tsx b/src/pages/park/index.tsx index 7085443..44bfd87 100644 --- a/src/pages/park/index.tsx +++ b/src/pages/park/index.tsx @@ -3,7 +3,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ParkTenantProvider, useParkTenant } from "./tenant-context"; -import { TenantTab } from "./tenant-tab"; +import { ScreensTab } from "./screens-tab"; import { ScreenTab } from "./screen-tab"; import { CompaniesTab } from "./companies-tab"; import { KbTab } from "./kb-tab"; @@ -18,14 +18,12 @@ function TenantBar() { 当前园区:{current?.name || tenantId || "未选择"} -
- -
+ ); } @@ -34,20 +32,20 @@ function ParkPageInner() { const { tenantId } = useParkTenant(); return (
- + - + - 园区配置 + 屏幕管理 大屏控制台 入驻企业 园区知识库 园区智能体 大屏数据 - + diff --git a/src/pages/park/screens-tab.tsx b/src/pages/park/screens-tab.tsx new file mode 100644 index 0000000..6a92fd9 --- /dev/null +++ b/src/pages/park/screens-tab.tsx @@ -0,0 +1,74 @@ +import * as React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { DataTable, type Column } from "@/components/generic/DataTable"; +import { useResource } from "@/components/generic/useResource"; +import { parkApi } from "@/api/park"; +import { useParkTenant } from "./tenant-context"; +import type { ParkScreen } from "@/types"; + +const EMPTY = { device_id: "", name: "", role: "main", location: "" }; + +export function ScreensTab() { + const { tenantId } = useParkTenant(); + const res = useResource(() => parkApi.screens(tenantId)); + const [form, setForm] = React.useState(EMPTY); + const [busy, setBusy] = React.useState(false); + + const create = async () => { + setBusy(true); + try { + await parkApi.createScreen(tenantId, form); + setForm(EMPTY); + res.reload(); + } finally { setBusy(false); } + }; + const remove = async (id: string) => { + if (!window.confirm("确认删除该屏幕?")) return; + await parkApi.deleteScreen(tenantId, id); + res.reload(); + }; + + const columns: Column[] = [ + { key: "device_id", header: "设备 ID", cell: (r) => {r.device_id} }, + { key: "name", header: "名称", cell: (r) => r.name || "-" }, + { key: "role", header: "角色", cell: (r) => (r.role === "secondary" ? "副屏" : "主屏") }, + { key: "location", header: "位置", cell: (r) => r.location || "-" }, + { key: "created_at", header: "登记", cell: (r) => r.created_at || "-" }, + { key: "actions", header: "操作", cell: (r) => }, + ]; + + return ( +
+

登记本园区的屏幕设备(device_id 对应该大屏的 MQTT client_id),用于精准控制大屏。

+ {res.error ?

{res.error}

: null} + + 已登记屏幕({res.data.length}) + + + + + + 新增屏幕 + +
setForm((f) => ({ ...f, device_id: e.target.value }))} placeholder="大屏的 MQTT client_id" />
+
setForm((f) => ({ ...f, name: e.target.value }))} />
+
+ +
+
setForm((f) => ({ ...f, location: e.target.value }))} />
+
+ +
+
+ ); +} diff --git a/src/types.ts b/src/types.ts index 827b67a..5923990 100644 --- a/src/types.ts +++ b/src/types.ts @@ -166,6 +166,15 @@ export interface Tenant { username?: string; } +export interface ParkScreen { + id: string; + device_id: string; + name?: string; + role?: string; // main | secondary + location?: string; + created_at?: string; +} + export interface DisplayDevice { device_id: string; role?: string; diff --git a/vite.config.ts b/vite.config.ts index 042da8e..32d0f14 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,8 +22,10 @@ export default defineConfig(({ mode }) => { "/api": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, "/auth": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, "/admin": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, - // 仅代理 /park/api(园区 API),不要把 SPA 路由 /park 代理到后端,否则刷新返回 404 + // 园区 SaaS API 子路径(勿代理 SPA 路由 /park 本身,否则刷新 404) "/park/api": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, + "/park/tenants": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, + "/park/auth": { target: env.VITE_API_PROXY || "http://127.0.0.1:8090", changeOrigin: true }, }, }, };