change to next
This commit is contained in:
209
web/src/app/view/components/ViewEditor.tsx
Normal file
209
web/src/app/view/components/ViewEditor.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user