210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
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>
|
||
)
|
||
}
|