From f70feb360cc10e077962a1fac07540d2a276d49b Mon Sep 17 00:00:00 2001 From: attacker Date: Mon, 15 Jun 2026 08:07:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B5=84=E6=BA=90=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E9=87=8D=E6=9E=84=E2=80=94=E2=80=94=E6=B8=85?= =?UTF-8?q?=E7=90=86816=E8=A1=8C=E6=AD=BB=E4=BB=A3=E7=A0=81+=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E5=85=B1=E4=BA=ABhelpers+=E4=BF=AE=E5=A4=8D=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=A0=87=E7=AD=BEbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 CabinetView.tsx(422行) + ResourceModelManager.tsx(394行) 死代码 - 提取 approval/helpers.tsx 共享辅助函数(parseSteps/parseFields/renderFormData/ getProgressInfo/renderStepProgress/APPROVAL_TYPE_OPTIONS/getInstanceType) - 修复审批类型标签bug:instance无type字段,需从template.type取 (getApprovalTypeLabel/Color/Icon 全部改用 getInstanceType(instance)) - ApprovalFlow.tsx 771→666行(低于700限制) - ApprovalProcessTab.tsx 移除重复helpers+过期类型字典(deploy/server/permission→server_apply/app_deploy) --- .../src/components/approval/ApprovalFlow.tsx | 135 +----- .../approval/ApprovalProcessTab.tsx | 96 +--- frontend/src/components/approval/helpers.tsx | 128 ++++++ .../src/components/server/CabinetView.tsx | 422 ------------------ .../server/ResourceModelManager.tsx | 394 ---------------- 5 files changed, 155 insertions(+), 1020 deletions(-) create mode 100644 frontend/src/components/approval/helpers.tsx delete mode 100644 frontend/src/components/server/CabinetView.tsx delete mode 100644 frontend/src/components/server/ResourceModelManager.tsx diff --git a/frontend/src/components/approval/ApprovalFlow.tsx b/frontend/src/components/approval/ApprovalFlow.tsx index 512d03d..7902e7a 100644 --- a/frontend/src/components/approval/ApprovalFlow.tsx +++ b/frontend/src/components/approval/ApprovalFlow.tsx @@ -22,133 +22,28 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { - GitBranch, FileText, CheckCircle2, XCircle, Loader2, Clock, Plus, - X, - GitCommit, - Server, - KeyRound, } from 'lucide-react'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { approvalApi } from '@/services/api'; import { ApprovalTemplateTab } from './ApprovalTemplateTab'; import { ApprovalProcessTab } from './ApprovalProcessTab'; - -// ---- Shared helpers (kept in main for dialog usage) ---- - -function parseSteps(steps: any) { - if (Array.isArray(steps)) return steps; - if (typeof steps === 'string') { - try { - const parsed = JSON.parse(steps); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } - } - return []; -} - -// 解析模板的 form_schema,返回字段定义数组 -function parseFields(formSchema: any): any[] { - if (!formSchema) return []; - const schema = typeof formSchema === 'string' ? (() => { try { return JSON.parse(formSchema); } catch { return null; } })() : formSchema; - if (!schema) return []; - return Array.isArray(schema.fields) ? schema.fields : []; -} - -// 在详情弹窗中渲染表单数据为键值对展示 -function renderFormData(formDataStr: string, template?: any) { - if (!formDataStr) return null; - let data: Record; - try { - data = typeof formDataStr === 'string' ? JSON.parse(formDataStr) : formDataStr; - } catch { - return null; - } - if (!data || typeof data !== 'object') return null; - - // 优先使用模板的 form_schema 标签,否则退化为 key - const fields = parseFields(template?.form_schema); - const labelMap = new Map(); - fields.forEach((f) => labelMap.set(f.key, f.label)); - - const entries = Object.entries(data).filter(([, v]) => v !== null && v !== undefined && v !== ''); - if (entries.length === 0) return null; - - return ( -
- {entries.map(([key, value]) => ( -
-

{labelMap.get(key) || key}

-

{String(value)}

-
- ))} -
- ); -} - -const APPROVAL_TYPE_OPTIONS = [ - { value: 'server_apply', label: '云服务器申请', icon: Server, color: 'var(--dg-cyan)' }, - { value: 'app_deploy', label: '应用部署申请', icon: GitCommit, color: 'var(--dg-blue)' }, -]; - -const getApprovalTypeLabel = (value: string): string => - APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.label || value || '通用审批'; - -const getApprovalTypeColor = (value: string): string => - APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.color || 'var(--dg-text-secondary)'; - -function getProgressInfo(instance: any) { - const steps = parseSteps(instance.steps); - const currentIndex = instance.current_step ?? 0; - const total = steps.length; - const completed = steps.filter((s: any) => s.status === 'approved' || s.status === 'completed').length; - const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; - return { steps, currentIndex, total, completed, percentage }; -} - -// 渲染审批进度(用于详情弹窗) -function renderStepProgress(instance: any) { - const { steps, currentIndex } = getProgressInfo(instance); - if (steps.length === 0) return null; - return ( -
- {steps.map((step: any, index: number) => { - const isCompleted = step.status === 'approved' || step.status === 'completed'; - const isCurrent = index === currentIndex; - const isRejected = step.status === 'rejected'; - return ( -
-
- {isCompleted && } - {isRejected && } - {isCurrent && !isRejected && } - {step.name || step.approver_name || `步骤 ${index + 1}`} -
- {index < steps.length - 1 && ( -
- )} -
- ); - })} -
- ); -} +import { + parseFields, + renderFormData, + renderStepProgress, + getProgressInfo, + getApprovalTypeLabel, + getApprovalTypeColor, + getInstanceType, + APPROVAL_TYPE_OPTIONS, +} from './helpers'; export function ApprovalFlow() { const confirm = useConfirm(); @@ -427,14 +322,14 @@ export function ApprovalFlow() {
-
+

{instance.title}

- - {getApprovalTypeLabel(instance.type)} + + {getApprovalTypeLabel(getInstanceType(instance))}

@@ -494,8 +389,8 @@ export function ApprovalFlow() {

{selectedInstance.title}

- - {getApprovalTypeLabel(selectedInstance.type)} + + {getApprovalTypeLabel(getInstanceType(selectedInstance))} 申请人:{selectedInstance.applicant_name || selectedInstance.applicant || '--'} diff --git a/frontend/src/components/approval/ApprovalProcessTab.tsx b/frontend/src/components/approval/ApprovalProcessTab.tsx index 239bd3e..f8928a4 100644 --- a/frontend/src/components/approval/ApprovalProcessTab.tsx +++ b/frontend/src/components/approval/ApprovalProcessTab.tsx @@ -9,40 +9,16 @@ import { XCircle, Eye, Loader2, - GitCommit, - Server, - KeyRound, FileText, } from 'lucide-react'; - -function parseSteps(steps: any) { - if (Array.isArray(steps)) return steps; - if (typeof steps === 'string') { - try { - const parsed = JSON.parse(steps); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } - } - return []; -} - -// 审批类型字典 -const APPROVAL_TYPE_OPTIONS = [ - { value: 'deploy', label: '发布审批', icon: GitCommit, color: 'var(--dg-blue)' }, - { value: 'server', label: '服务器操作', icon: Server, color: 'var(--dg-cyan)' }, - { value: 'permission', label: '权限申请', icon: KeyRound, color: 'var(--dg-purple)' }, -]; - -const getApprovalTypeLabel = (value: string): string => - APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.label || value || '通用审批'; - -const getApprovalTypeColor = (value: string): string => - APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.color || 'var(--dg-text-secondary)'; - -const getApprovalTypeIcon = (value: string): React.ElementType => - APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.icon || FileText; +import { + getProgressInfo, + renderStepProgress, + getApprovalTypeLabel, + getApprovalTypeColor, + getApprovalTypeIcon, + getInstanceType, +} from './helpers'; // 状态标签页定义 const STATUS_TABS = [ @@ -92,54 +68,6 @@ function getStatusBadge(status: string) { ); } -// 获取进度信息 -function getProgressInfo(instance: any) { - const steps = parseSteps(instance.steps); - const currentIndex = instance.current_step ?? 0; - const total = steps.length; - const completed = steps.filter((s: any) => s.status === 'approved' || s.status === 'completed').length; - const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; - return { steps, currentIndex, total, completed, percentage }; -} - -// 渲染审批进度 -function renderStepProgress(instance: any) { - const { steps, currentIndex } = getProgressInfo(instance); - if (steps.length === 0) return null; - return ( -
- {steps.map((step: any, index: number) => { - const isCompleted = step.status === 'approved' || step.status === 'completed'; - const isCurrent = index === currentIndex; - const isRejected = step.status === 'rejected'; - return ( -
-
- {isCompleted && } - {isRejected && } - {isCurrent && !isRejected && ( - - )} - {step.name || step.approver_name || `步骤 ${index + 1}`} -
- {index < steps.length - 1 && ( -
- )} -
- ); - })} -
- ); -} - interface ApprovalProcessTabProps { processes: any[]; filteredInstances: any[]; @@ -218,14 +146,14 @@ export function ApprovalProcessTab({ ) : (
{filteredInstances.map((instance) => { - const Icon = getApprovalTypeIcon(instance.type); + const Icon = getApprovalTypeIcon(getInstanceType(instance)); const { percentage } = getProgressInfo(instance); return (
-
+
@@ -235,8 +163,8 @@ export function ApprovalProcessTab({
- - {getApprovalTypeLabel(instance.type)} + + {getApprovalTypeLabel(getInstanceType(instance))} 申请人:{instance.applicant_name || instance.applicant || '--'} diff --git a/frontend/src/components/approval/helpers.tsx b/frontend/src/components/approval/helpers.tsx new file mode 100644 index 0000000..f809fb7 --- /dev/null +++ b/frontend/src/components/approval/helpers.tsx @@ -0,0 +1,128 @@ +// 资源申请模块共享辅助函数(从 ApprovalFlow.tsx 提取) // Logan.Li +import { cn } from '@/lib/utils'; +import { + CheckCircle2, + XCircle, + Loader2, + Server, + GitCommit, + FileText, +} from 'lucide-react'; + +// 解析审批步骤数组(兼容 JSON 字符串) // Logan.Li +export function parseSteps(steps: any) { + if (Array.isArray(steps)) return steps; + if (typeof steps === 'string') { + try { + const parsed = JSON.parse(steps); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } + } + return []; +} + +// 解析模板的 form_schema,返回字段定义数组 // Logan.Li +export function parseFields(formSchema: any): any[] { + if (!formSchema) return []; + const schema = typeof formSchema === 'string' ? (() => { try { return JSON.parse(formSchema); } catch { return null; } })() : formSchema; + if (!schema) return []; + return Array.isArray(schema.fields) ? schema.fields : []; +} + +// 在详情弹窗中渲染表单数据为键值对展示 // Logan.Li +export function renderFormData(formDataStr: string, template?: any) { + if (!formDataStr) return null; + let data: Record; + try { + data = typeof formDataStr === 'string' ? JSON.parse(formDataStr) : formDataStr; + } catch { + return null; + } + if (!data || typeof data !== 'object') return null; + + const fields = parseFields(template?.form_schema); + const labelMap = new Map(); + fields.forEach((f) => labelMap.set(f.key, f.label)); + + const entries = Object.entries(data).filter(([, v]) => v !== null && v !== undefined && v !== ''); + if (entries.length === 0) return null; + + return ( +
+ {entries.map(([key, value]) => ( +
+

{labelMap.get(key) || key}

+

{String(value)}

+
+ ))} +
+ ); +} + +// 审批类型选项 // Logan.Li +export const APPROVAL_TYPE_OPTIONS = [ + { value: 'server_apply', label: '云服务器申请', icon: Server, color: 'var(--dg-cyan)' }, + { value: 'app_deploy', label: '应用部署申请', icon: GitCommit, color: 'var(--dg-blue)' }, +]; + +export const getApprovalTypeLabel = (value: string): string => + APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.label || value || '通用审批'; + +export const getApprovalTypeColor = (value: string): string => + APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.color || 'var(--dg-text-secondary)'; + +// 根据审批类型返回对应图标组件 // Logan.Li +export const getApprovalTypeIcon = (value: string) => + APPROVAL_TYPE_OPTIONS.find((o) => o.value === value)?.icon || FileText; + +// 获取审批实例的类型(优先从关联模板取) // Logan.Li +export const getInstanceType = (instance: any): string => + instance?.template?.type || instance?.type || ''; + +// 计算审批进度信息 // Logan.Li +export function getProgressInfo(instance: any) { + const steps = parseSteps(instance.steps); + const currentIndex = instance.current_step ?? 0; + const total = steps.length; + const completed = steps.filter((s: any) => s.status === 'approved' || s.status === 'completed').length; + const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; + return { steps, currentIndex, total, completed, percentage }; +} + +// 渲染审批进度(用于详情弹窗) // Logan.Li +export function renderStepProgress(instance: any) { + const { steps, currentIndex } = getProgressInfo(instance); + if (steps.length === 0) return null; + return ( +
+ {steps.map((step: any, index: number) => { + const isCompleted = step.status === 'approved' || step.status === 'completed'; + const isCurrent = index === currentIndex; + const isRejected = step.status === 'rejected'; + return ( +
+
+ {isCompleted && } + {isRejected && } + {isCurrent && !isRejected && } + {step.name || step.approver_name || `步骤 ${index + 1}`} +
+ {index < steps.length - 1 && ( +
+ )} +
+ ); + })} +
+ ); +} diff --git a/frontend/src/components/server/CabinetView.tsx b/frontend/src/components/server/CabinetView.tsx deleted file mode 100644 index 5ff8d1a..0000000 --- a/frontend/src/components/server/CabinetView.tsx +++ /dev/null @@ -1,422 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - Server, - Plus, - Search, - Grid3X3, - List, - MapPin, - Cpu, - MemoryStick, - HardDrive, - Eye, - Edit, -} from 'lucide-react'; -import { serverApi } from '@/services/api'; -import type { Server as ServerType } from '@/types'; - -export function CabinetView() { - const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); - const [statusFilter, setStatusFilter] = useState('all'); - const [searchQuery, setSearchQuery] = useState(''); - const [servers, setServers] = useState([]); - const [loading, setLoading] = useState(true); - const [selectedServer, setSelectedServer] = useState(null); - - // 拉取服务器列表 // Logan.Li - const fetchServers = async () => { - setLoading(true); - try { - const resp = await serverApi.getServers({ page: 1, page_size: 200 }); - setServers((resp?.list ?? []) as ServerType[]); - } catch { - setServers([]); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchServers(); - }, []); - - const filteredServers = servers.filter((srv) => { - const matchesStatus = statusFilter === 'all' || srv.status === statusFilter; - const matchesSearch = - srv.hostname?.toLowerCase().includes(searchQuery.toLowerCase()) || - srv.ip?.toLowerCase().includes(searchQuery.toLowerCase()); - return matchesStatus && matchesSearch; - }); - - const getStatusColor = (status: string) => { - const colors: Record = { - running: 'border-emerald-500/30 text-emerald-400 bg-emerald-500/10', - stopped: 'border-red-500/30 text-red-400 bg-red-500/10', - maintenance: 'border-amber-500/30 text-amber-400 bg-amber-500/10', - offline: 'border-slate-500/30 text-slate-400 bg-slate-500/10', - }; - return colors[status] ?? colors.offline; - }; - - const getStatusText = (status: string) => { - const map: Record = { - running: '运行中', - stopped: '已停止', - maintenance: '维护中', - offline: '离线', - }; - return map[status] ?? status; - }; - - const getUsageColor = (usage: number) => - usage > 80 ? 'bg-red-400' : usage > 60 ? 'bg-amber-400' : 'bg-emerald-400'; - - return ( -
- {/* Page Header */} -
-
-

机房机柜视图

-

数据中心服务器资产概览

-
- -
- - {/* Stats Cards */} -
- - -
-
-

服务器总数

-

{servers.length}

-
-
- -
-
-
-
- - -
-
-

运行中

-

- {servers.filter((s) => s.status === 'running').length} -

-
-
- -
-
-
-
- - -
-
-

维护中

-

- {servers.filter((s) => s.status === 'maintenance').length} -

-
-
- -
-
-
-
- - -
-
-

离线

-

- {servers.filter((s) => s.status === 'offline' || s.status === 'stopped').length} -

-
-
- -
-
-
-
-
- - {/* Filters & View Toggle */} -
-
-
- - setSearchQuery(e.target.value)} - className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-500" - /> -
- -
-
- - -
-
- - {/* View Content */} - {loading ? ( -
-
-
- ) : filteredServers.length === 0 ? ( -
- -

暂无服务器数据

-
- ) : viewMode === 'grid' ? ( -
- {filteredServers.map((srv) => ( - setSelectedServer(srv)} - > - -
-
-
- -
-
-

{srv.hostname || '--'}

-

{srv.ip || '--'}

-
-
- - {getStatusText(srv.status)} - -
-
-
-
- - CPU - - {Math.round(srv.cpuUsage ?? 0)}% -
-
-
-
-
-
-
- - 内存 - - {Math.round(srv.memoryUsage ?? 0)}% -
-
-
-
-
-
-
- - 磁盘 - - {Math.round(srv.diskUsage ?? 0)}% -
-
-
-
-
-
-
- - {srv.cpu ?? 0} 核 - - - {srv.memory ?? 0} GB - - {srv.region && ( - - {srv.region} - - )} -
- - - ))} -
- ) : ( - - -
- - - - - - - - - - - - - - {filteredServers.map((srv) => ( - - - - - - - - - - ))} - -
主机名IP地址系统区域配置状态操作
-
- - {srv.hostname || '--'} -
-
{srv.ip || '--'}{srv.os || '--'}{srv.region || '--'} - {srv.cpu ?? 0}核 / {srv.memory ?? 0}GB - - - {getStatusText(srv.status)} - - -
- - -
-
-
-
-
- )} - - {/* Server Detail Dialog */} - setSelectedServer(null)}> - - - - - {selectedServer?.hostname} - - - {selectedServer?.ip} · {selectedServer?.os} - - - -
-
-
-

IP 地址

-

{selectedServer?.ip || '--'}

-
-
-

操作系统

-

{selectedServer?.os || '--'}

-
-
-

配置

-

- {selectedServer?.cpu ?? 0} 核 / {selectedServer?.memory ?? 0} GB -

-
-
-

状态

- - {selectedServer ? getStatusText(selectedServer.status) : ''} - -
-
-
- - - - - -
-
-
- ); -} diff --git a/frontend/src/components/server/ResourceModelManager.tsx b/frontend/src/components/server/ResourceModelManager.tsx deleted file mode 100644 index 87570ae..0000000 --- a/frontend/src/components/server/ResourceModelManager.tsx +++ /dev/null @@ -1,394 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Badge } from '@/components/ui/badge'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Plus, - Search, - Trash2, - Pencil, - Layers, - GripVertical, - X, - Loader2, -} from 'lucide-react'; -import { resourceModelApi } from '@/services/api'; - -interface AttrDef { - name: string; - code: string; - type: string; - required: boolean; - unique?: boolean; -} - -const attrTypes = [ - { value: 'string', label: '字符串' }, - { value: 'number', label: '数字' }, - { value: 'bool', label: '布尔' }, - { value: 'date', label: '日期' }, - { value: 'enum', label: '枚举' }, -]; - -const typeLabels: Record = { - normal: '普通模型', - parent: '父模型', -}; - -export function ResourceModelManager() { - const [models, setModels] = useState([]); - const [loading, setLoading] = useState(true); - const [searchQuery, setSearchQuery] = useState(''); - const [dialogOpen, setDialogOpen] = useState(false); - const [editingItem, setEditingItem] = useState(null); - const [submitting, setSubmitting] = useState(false); - const [selectedModel, setSelectedModel] = useState(null); - const [formData, setFormData] = useState({ - name: '', code: '', description: '', type: 'normal', icon: '', - }); - const [attrs, setAttrs] = useState([]); - - const loadData = useCallback(async () => { - setLoading(true); - try { - const res = await resourceModelApi.list(); - const items = (res as any)?.list || []; - setModels(items); - } catch { - setModels([]); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { loadData(); }, [loadData]); - - const filtered = models.filter(m => - m.name?.toLowerCase().includes(searchQuery.toLowerCase()) || - m.code?.toLowerCase().includes(searchQuery.toLowerCase()) - ); - - const parseAttrs = (attrsStr: string): AttrDef[] => { - if (!attrsStr) return []; - try { - const parsed = JSON.parse(attrsStr); - return Array.isArray(parsed) ? parsed : []; - } catch { - return []; - } - }; - - const openCreate = () => { - setEditingItem(null); - setFormData({ name: '', code: '', description: '', type: 'normal', icon: '' }); - setAttrs([]); - setDialogOpen(true); - }; - - const openEdit = (item: any) => { - setEditingItem(item); - setFormData({ - name: item.name || '', code: item.code || '', description: item.description || '', - type: item.type || 'normal', icon: item.icon || '', - }); - setAttrs(parseAttrs(item.attributes)); - setDialogOpen(true); - }; - - const addAttr = () => { - setAttrs([...attrs, { name: '', code: '', type: 'string', required: false }]); - }; - - const updateAttr = (index: number, field: keyof AttrDef, value: any) => { - const next = [...attrs]; - (next[index] as any)[field] = value; - setAttrs(next); - }; - - const removeAttr = (index: number) => { - setAttrs(attrs.filter((_, i) => i !== index)); - }; - - const handleSubmit = async () => { - if (!formData.name.trim() || !formData.code.trim()) return; - setSubmitting(true); - try { - const payload = { ...formData, attributes: attrs.filter(a => a.name.trim() && a.code.trim()) }; - if (editingItem) { - await resourceModelApi.update(editingItem.id, payload); - } else { - await resourceModelApi.create(payload); - } - setDialogOpen(false); - loadData(); - } catch { - } finally { - setSubmitting(false); - } - }; - - const [deleteId, setDeleteId] = useState(null); - const handleDelete = (id: number) => setDeleteId(id); - const confirmDelete = async () => { - if (deleteId === null) return; - await resourceModelApi.delete(deleteId); - if (selectedModel?.id === deleteId) setSelectedModel(null); - setDeleteId(null); - loadData(); - }; - - const getAttrCount = (item: any) => parseAttrs(item.attributes).length; - - return ( -
- {/* Toolbar */} -
-
- - setSearchQuery(e.target.value)} - className="pl-9 h-9 bg-slate-800/50 border-slate-700" - /> -
- -
- - {/* List */} - {loading ? ( -
- -
- ) : filtered.length === 0 ? ( -
- -

暂无资源模型

-
- ) : ( -
- {filtered.map((m) => ( -
setSelectedModel(selectedModel?.id === m.id ? null : m)} - > -
-
-
- -
-
-

{m.name}

- {m.code} -
-
-
- - -
-
- {m.description && ( -

{m.description}

- )} -
- - {typeLabels[m.type] || m.type} - - - {getAttrCount(m)} 个属性 - -
- - {/* Expanded attributes */} - {selectedModel?.id === m.id && ( -
- {parseAttrs(m.attributes).map((attr, i) => ( -
- {attr.name} -
- {attr.code} - {attr.type} - {attr.required && *} -
-
- ))} - {getAttrCount(m) === 0 && ( -

暂无属性定义

- )} -
- )} -
- ))} -
- )} - - {/* Dialog */} - - - - {editingItem ? '编辑资源模型' : '添加资源模型'} - -
-
-
- - setFormData({ ...formData, name: e.target.value })} - placeholder="如:服务器" - className="bg-slate-800/50 border-slate-700" - /> -
-
- - setFormData({ ...formData, code: e.target.value })} - placeholder="如:server" - className="bg-slate-800/50 border-slate-700 font-mono" - disabled={!!editingItem} - /> -
-
-
-
- - -
-
- - setFormData({ ...formData, description: e.target.value })} - placeholder="模型描述..." - className="bg-slate-800/50 border-slate-700" - /> -
-
- - {/* Attributes editor */} -
-
- - -
-
- {attrs.map((attr, i) => ( -
- - updateAttr(i, 'name', e.target.value)} - className="h-8 bg-slate-800/50 border-slate-700 flex-1" - /> - updateAttr(i, 'code', e.target.value)} - className="h-8 bg-slate-800/50 border-slate-700 flex-1 font-mono text-sm" - /> - - - -
- ))} - {attrs.length === 0 && ( -

暂无属性,点击"添加属性"开始定义

- )} -
-
-
- - - - -
-
- - {/* 删除确认 */} - !v && setDeleteId(null)}> - - - 确认删除 - -

确定删除此资源模型?此操作不可撤销。

- - - - -
-
-
- ); -}