Files
router-studio/web/src/app/view/components/ViewEditor.tsx
2026-01-15 12:32:07 +08:00

210 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useRef } from "react"
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { DataItemForm } from "@/app/view/components/DataItemForm"
import { ViewFormItem } from "@/app/view/components/ViewFormItem"
import { nanoid } from "nanoid"
interface ViewEditorProps {
open: boolean
onOpenChange: (open: boolean) => void
data?: {
id?: string
title?: string
data?: { items: any[] }
views?: any[]
}
onSave: (data: any) => void
}
export const ViewEditor = ({ open, onOpenChange, data, onSave }: ViewEditorProps) => {
const [title, setTitle] = useState('')
const [dataItems, setDataItems] = useState<any[]>([])
const [views, setViews] = useState<any[]>([])
const dataItemsScrollRef = useRef<HTMLDivElement>(null)
const viewsScrollRef = useRef<HTMLDivElement>(null)
const isUpdate = !!data?.id
useEffect(() => {
if (open) {
setTitle(data?.title || '')
setDataItems(data?.data?.items || [])
setViews(data?.views || [])
}
}, [open, data])
const handleAddDataItem = () => {
setDataItems([...dataItems, { type: 'api', api: { url: '' } }])
// 异步滚动到底部,使用 smooth 平滑滚动
setTimeout(() => {
if (dataItemsScrollRef.current) {
dataItemsScrollRef.current.scrollTo({
top: dataItemsScrollRef.current.scrollHeight,
behavior: 'smooth'
})
}
}, 0)
}
const handleUpdateDataItem = (index: number, item: any) => {
const newItems = [...dataItems]
newItems[index] = item
setDataItems(newItems)
}
const handleRemoveDataItem = (index: number) => {
setDataItems(dataItems.filter((_, i) => i !== index))
}
const handleAddView = () => {
setViews([...views, { id: nanoid(16), title: '', query: '' }])
// 异步滚动到底部,使用 smooth 平滑滚动
setTimeout(() => {
if (viewsScrollRef.current) {
viewsScrollRef.current.scrollTo({
top: viewsScrollRef.current.scrollHeight,
behavior: 'smooth'
})
}
}, 0)
}
const handleUpdateView = (index: number, view: any) => {
const newViews = [...views]
newViews[index] = view
setViews(newViews)
}
const handleRemoveView = (index: number) => {
setViews(views.filter((_, i) => i !== index))
}
const handleSave = () => {
const pickData = dataItems.map(item => {
if (item.type === 'api') {
delete item.api.query
}
if (item.type === 'worker') {
delete item.worker.worker
}
if (item.type === 'context') {
delete item.context.router
}
if (item.type === 'page') {
}
return item
})
const viewData = {
id: data?.id,
title,
data: {
items: pickData
},
views
}
onSave(viewData)
onOpenChange(false)
}
const handleClose = () => {
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{isUpdate ? '编辑视图' : '新增视图'}</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 固定的视图标题 */}
<div className="space-y-2">
<Label htmlFor="title"></Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="输入视图标题"
/>
</div>
{/* Tabs 容器 */}
<Tabs defaultValue="data" className="w-full flex flex-col">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="data"></TabsTrigger>
<TabsTrigger value="views"></TabsTrigger>
</TabsList>
{/* 数据项配置 Tab */}
<TabsContent value="data" className="flex flex-col mt-4">
<div className="flex justify-between items-center mb-4">
<h3 className="font-medium"> (data.items)</h3>
<Button type="button" variant="outline" size="sm" onClick={handleAddDataItem} className="cursor-pointer">
</Button>
</div>
<div ref={dataItemsScrollRef} className="space-y-4 max-h-[50vh] overflow-y-auto pr-4">
{dataItems.length === 0 ? (
<div className="text-center text-sm text-gray-500 py-8">
"添加数据项"
</div>
) : (
dataItems.map((item, index) => (
<DataItemForm
key={index}
item={item}
onChange={(newItem) => handleUpdateDataItem(index, newItem)}
onRemove={() => handleRemoveDataItem(index)}
/>
))
)}
</div>
</TabsContent>
{/* 视图配置 Tab */}
<TabsContent value="views" className="flex flex-col mt-4">
<div className="flex justify-between items-center mb-4">
<h3 className="font-medium"> (views)</h3>
<Button type="button" variant="outline" size="sm" onClick={handleAddView} className="cursor-pointer">
</Button>
</div>
<div ref={viewsScrollRef} className="space-y-4 max-h-[50vh] overflow-y-auto pr-4">
{views.length === 0 ? (
<div className="text-center text-sm text-gray-500 py-8">
"添加视图"
</div>
) : (
views.map((view, index) => (
<ViewFormItem
key={view.id || index}
view={view}
onChange={(newView) => handleUpdateView(index, newView)}
onRemove={() => handleRemoveView(index)}
/>
))
)}
</div>
</TabsContent>
</Tabs>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={handleClose} className="cursor-pointer">
</Button>
<Button type="button" onClick={handleSave} className="cursor-pointer">
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}