refactor: 重构workspaces页面UI,使用组件化设计和Tailwind样式

This commit is contained in:
xiongxiao
2026-03-19 02:17:43 +08:00
committed by cnb
parent 330accb822
commit 3f0733a540
6 changed files with 355 additions and 514 deletions

View File

@@ -0,0 +1,105 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { TagInput } from "./TagInput";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function CreateDialog({ open, onClose, onSubmit }: {
open: boolean;
onClose: () => void;
onSubmit: (data: { title: string, tags?: string[], link?: string, summary?: string, description?: string }) => Promise<void>;
}) {
const [title, setTitle] = useState('');
const [tags, setTags] = useState<string[]>([]);
const [link, setLink] = useState('');
const [summary, setSummary] = useState('');
const [description, setDescription] = useState('');
const [submitting, setSubmitting] = useState(false);
const handleSubmit = async () => {
if (!title.trim()) {
alert('请输入标题');
return;
}
setSubmitting(true);
try {
await onSubmit({
title: title.trim(),
tags,
link: link.trim(),
summary: summary.trim(),
description: description.trim(),
});
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Workspace</DialogTitle>
<DialogDescription>Workspace</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="create-title"></Label>
<Input
id="create-title"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="请输入标题"
/>
</div>
<div className="space-y-2">
<Label></Label>
<TagInput value={tags} onChange={setTags} placeholder="输入标签后按回车添加" />
</div>
<div className="space-y-2">
<Label htmlFor="create-link"></Label>
<Input
id="create-link"
value={link}
onChange={e => setLink(e.target.value)}
placeholder="https://..."
/>
</div>
<div className="space-y-2">
<Label htmlFor="create-summary"></Label>
<Input
id="create-summary"
value={summary}
onChange={e => setSummary(e.target.value)}
placeholder="简要描述"
/>
</div>
<div className="space-y-2">
<Label htmlFor="create-description"></Label>
<Textarea
id="create-description"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="详细描述..."
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={submitting}></Button>
<Button variant="outline" onClick={handleSubmit} disabled={submitting}>
{submitting ? '创建中...' : '创建'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,117 @@
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { TagInput } from "./TagInput";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function EditDialog({ open, item, onClose, onSubmit }: {
open: boolean;
item: any;
onClose: () => void;
onSubmit: (id: string, data: { title?: string, tags?: string[], link?: string, summary?: string, description?: string }) => Promise<void>;
}) {
const [title, setTitle] = useState('');
const [tags, setTags] = useState<string[]>([]);
const [link, setLink] = useState('');
const [summary, setSummary] = useState('');
const [description, setDescription] = useState('');
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (open && item) {
setTitle(item.title || '');
setTags(item.tags || []);
setLink(item.link || '');
setSummary(item.summary || '');
setDescription(item.description || '');
}
}, [open, item]);
const handleSubmit = async () => {
if (!title.trim()) {
alert('请输入标题');
return;
}
if (!item?.id) return;
setSubmitting(true);
try {
await onSubmit(item.id, {
title: title.trim(),
tags,
link: link.trim(),
summary: summary.trim(),
description: description.trim(),
});
} finally {
setSubmitting(false);
}
};
return (
<Dialog open={open} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-lg!">
<DialogHeader>
<DialogTitle>Workspace</DialogTitle>
<DialogDescription>Workspace信息</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="edit-title"></Label>
<Input
id="edit-title"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="请输入标题"
/>
</div>
<div className="space-y-2">
<Label></Label>
<TagInput value={tags} onChange={setTags} placeholder="输入标签后按回车添加" />
</div>
<div className="space-y-2">
<Label htmlFor="edit-link"></Label>
<Input
id="edit-link"
value={link}
onChange={e => setLink(e.target.value)}
placeholder="https://..."
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-summary"></Label>
<Input
id="edit-summary"
value={summary}
onChange={e => setSummary(e.target.value)}
placeholder="简要描述"
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-description"></Label>
<Textarea
id="edit-description"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="详细描述..."
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={submitting}></Button>
<Button variant="outline" onClick={handleSubmit} disabled={submitting}>
{submitting ? '保存中...' : '保存'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,53 @@
import { useState } from "react";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { XIcon } from "lucide-react";
export function TagInput({ value, onChange, placeholder }: {
value: string[];
onChange: (tags: string[]) => void;
placeholder?: string;
}) {
const [input, setInput] = useState('');
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && input.trim()) {
e.preventDefault();
if (!value.includes(input.trim())) {
onChange([...value, input.trim()]);
}
setInput('');
}
};
const handleRemove = (tag: string) => {
onChange(value.filter(t => t !== tag));
};
return (
<div className="space-y-2">
<Input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder || '输入标签后按回车添加'}
/>
{value.length > 0 && (
<div className="flex flex-wrap gap-1">
{value.map((tag) => (
<Badge key={tag} variant="outline" className="gap-1">
{tag}
<button
type="button"
onClick={() => handleRemove(tag)}
className="hover:text-destructive"
>
<XIcon className="size-3" />
</button>
</Badge>
))}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,3 @@
export { CreateDialog } from "./CreateDialog";
export { EditDialog } from "./EditDialog";
export { TagInput } from "./TagInput";

View File

@@ -1,6 +1,19 @@
import { useWorkspaceStore, type WorkspaceState } from "./store"; import { useWorkspaceStore, type WorkspaceState } from "./store";
import { useShallow } from "zustand/shallow"; import { useShallow } from "zustand/shallow";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import dayjs from "dayjs";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { CreateDialog, EditDialog } from "./components";
import { SearchIcon, RefreshCwIcon, PlusIcon, PencilIcon, TrashIcon } from "lucide-react";
export const App = () => { export const App = () => {
const workspaceStore = useWorkspaceStore(useShallow((state: WorkspaceState) => { const workspaceStore = useWorkspaceStore(useShallow((state: WorkspaceState) => {
@@ -45,100 +58,82 @@ export const App = () => {
}; };
return ( return (
<div style={{ padding: '20px' }}> <div className="p-5">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}> <div className="flex justify-between items-center mb-5">
<h1>Workspaces</h1> <h1 className="text-2xl font-semibold">Workspaces</h1>
<div style={{ display: 'flex', gap: '10px' }}> <div className="flex gap-2">
<input <div className="relative">
type="text" <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
placeholder="搜索workspace..." <Input
value={search} type="text"
onChange={(e) => setSearch(e.target.value)} placeholder="搜索workspace..."
style={{ padding: '8px 12px', border: '1px solid #ddd', borderRadius: '4px', width: '200px' }} value={search}
/> onChange={(e) => setSearch(e.target.value)}
<button className="pl-8 w-48"
onClick={handleRefresh} />
style={{ padding: '8px 16px', background: '#666', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }} </div>
> <Button variant="outline" size="sm" onClick={handleRefresh}>
<RefreshCwIcon className="size-4" />
</button> </Button>
<button <Button variant="outline" size="sm" onClick={handleCreate}>
onClick={handleCreate} <PlusIcon className="size-4" />
style={{ padding: '8px 16px', background: '#0070f3', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
>
Workspace Workspace
</button> </Button>
</div> </div>
</div> </div>
{workspaceStore.loading ? ( {workspaceStore.loading ? (
<div style={{ textAlign: 'center', padding: '40px' }}>...</div> <div className="text-center py-10 text-muted-foreground">...</div>
) : workspaceStore.list.length === 0 ? ( ) : workspaceStore.list.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>workspace数据</div> <div className="text-center py-10 text-muted-foreground">workspace数据</div>
) : ( ) : (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '16px' }}> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{workspaceStore.list.map((item) => ( {workspaceStore.list.map((item) => (
<div <Card key={item.id}>
key={item.id} <CardHeader>
style={{ <CardTitle>{item.title || '未命名'}</CardTitle>
border: '1px solid #e0e0e0', <CardDescription className="text-xs">ID: {item.id}</CardDescription>
borderRadius: '8px', </CardHeader>
padding: '16px', <CardContent className="space-y-2">
background: '#fff', {item.tags && item.tags.length > 0 && (
boxShadow: '0 2px 4px rgba(0,0,0,0.1)' <div className="flex flex-wrap gap-1">
}} {item.tags.map((tag, index) => (
> <Badge key={index} variant="outline">{tag}</Badge>
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px' }}>{item.title || '未命名'}</h3> ))}
<p style={{ margin: '0 0 8px 0', color: '#666', fontSize: '14px' }}>ID: {item.id}</p> </div>
<p style={{ margin: '0 0 8px 0', color: '#999', fontSize: '12px' }}> )}
: {item.created_at ? new Date(item.created_at).toLocaleString() : '-'} {item.summary && (
</p> <p className="text-sm text-muted-foreground">{item.summary}</p>
<p style={{ margin: '0 0 8px 0', color: '#999', fontSize: '12px' }}> )}
: {item.updated_at ? new Date(item.updated_at).toLocaleString() : '-'} {item.description && (
</p> <p className="text-sm text-muted-foreground line-clamp-2">{item.description}</p>
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}> )}
<button <p className="text-xs text-muted-foreground">
onClick={() => handleEdit(item)} : {item.createdAt ? dayjs(item.createdAt).format('YYYY-MM-DD HH:mm:ss') : '-'}
style={{ </p>
padding: '6px 12px', <p className="text-xs text-muted-foreground">
background: '#0070f3', : {item.updatedAt ? dayjs(item.updatedAt).format('YYYY-MM-DD HH:mm:ss') : '-'}
color: '#fff', </p>
border: 'none', <div className="flex gap-2 pt-2">
borderRadius: '4px', <Button variant="outline" size="sm" onClick={() => handleEdit(item)}>
cursor: 'pointer', <PencilIcon className="size-3.5" />
fontSize: '12px' </Button>
}} <Button variant="outline" size="sm" onClick={() => handleDelete(item.id)}>
> <TrashIcon className="size-3.5" />
</Button>
</button> </div>
<button </CardContent>
onClick={() => handleDelete(item.id)} </Card>
style={{
padding: '6px 12px',
background: '#ff4d4f',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px'
}}
>
</button>
</div>
</div>
))} ))}
</div> </div>
)} )}
{/* 创建弹窗 */}
<CreateDialog <CreateDialog
open={workspaceStore.showCreateDialog} open={workspaceStore.showCreateDialog}
onClose={() => workspaceStore.setShowCreateDialog(false)} onClose={() => workspaceStore.setShowCreateDialog(false)}
onSubmit={workspaceStore.createItem} onSubmit={workspaceStore.createItem}
/> />
{/* 编辑弹窗 */}
<EditDialog <EditDialog
open={workspaceStore.showEditDialog} open={workspaceStore.showEditDialog}
item={workspaceStore.editingItem} item={workspaceStore.editingItem}
@@ -152,434 +147,4 @@ export const App = () => {
); );
}; };
// 创建弹窗组件 export default App;
function CreateDialog({ open, onClose, onSubmit }: {
open: boolean;
onClose: () => void;
onSubmit: (data: { title: string, tags?: string[], link?: string, summary?: string, description?: string, data?: any }) => Promise<void>;
}) {
const [title, setTitle] = useState('');
const [tags, setTags] = useState('');
const [link, setLink] = useState('');
const [summary, setSummary] = useState('');
const [description, setDescription] = useState('');
const [data, setData] = useState('');
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (open) {
setTitle('');
setTags('');
setLink('');
setSummary('');
setDescription('');
setData('');
}
}, [open]);
const handleSubmit = async () => {
if (!title.trim()) {
alert('请输入标题');
return;
}
setSubmitting(true);
try {
let parsedData = {};
if (data.trim()) {
try {
parsedData = JSON.parse(data);
} catch {
alert('JSON格式不正确');
setSubmitting(false);
return;
}
}
await onSubmit({
title: title.trim(),
tags: tags.split(',').map(t => t.trim()).filter(Boolean),
link: link.trim(),
summary: summary.trim(),
description: description.trim(),
data: parsedData
});
} finally {
setSubmitting(false);
}
};
if (!open) return null;
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000
}} onClick={onClose}>
<div style={{
background: '#fff',
borderRadius: '8px',
padding: '24px',
width: '520px',
maxWidth: '90%',
maxHeight: '90vh',
overflow: 'auto'
}} onClick={e => e.stopPropagation()}>
<h2 style={{ margin: '0 0 20px 0' }}>Workspace</h2>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="请输入标题"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}> ()</label>
<input
type="text"
value={tags}
onChange={e => setTags(e.target.value)}
placeholder="标签1, 标签2, 标签3"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={link}
onChange={e => setLink(e.target.value)}
placeholder="https://..."
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={summary}
onChange={e => setSummary(e.target.value)}
placeholder="简要描述"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<textarea
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="详细描述..."
rows={3}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box',
resize: 'vertical'
}}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}> (JSON)</label>
<textarea
value={data}
onChange={e => setData(e.target.value)}
placeholder='{"key": "value"}'
rows={4}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box',
fontFamily: 'monospace',
resize: 'vertical'
}}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
<button
onClick={onClose}
style={{
padding: '8px 16px',
background: '#fff',
color: '#666',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
<button
onClick={handleSubmit}
disabled={submitting}
style={{
padding: '8px 16px',
background: '#0070f3',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: submitting ? 'not-allowed' : 'pointer',
opacity: submitting ? 0.7 : 1
}}
>
{submitting ? '创建中...' : '创建'}
</button>
</div>
</div>
</div>
);
}
// 编辑弹窗组件
function EditDialog({ open, item, onClose, onSubmit }: {
open: boolean;
item: any;
onClose: () => void;
onSubmit: (id: string, data: { title?: string, tags?: string[], link?: string, summary?: string, description?: string, data?: any }) => Promise<void>;
}) {
const [title, setTitle] = useState('');
const [tags, setTags] = useState('');
const [link, setLink] = useState('');
const [summary, setSummary] = useState('');
const [description, setDescription] = useState('');
const [data, setData] = useState('');
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (open && item) {
setTitle(item.title || '');
setTags(item.tags ? item.tags.join(', ') : '');
setLink(item.link || '');
setSummary(item.summary || '');
setDescription(item.description || '');
setData(item.data ? JSON.stringify(item.data, null, 2) : '');
}
}, [open, item]);
const handleSubmit = async () => {
if (!title.trim()) {
alert('请输入标题');
return;
}
if (!item?.id) return;
setSubmitting(true);
try {
let parsedData: any = undefined;
if (data.trim()) {
try {
parsedData = JSON.parse(data);
} catch {
alert('JSON格式不正确');
setSubmitting(false);
return;
}
}
await onSubmit(item.id, {
title: title.trim(),
tags: tags.split(',').map(t => t.trim()).filter(Boolean),
link: link.trim(),
summary: summary.trim(),
description: description.trim(),
data: parsedData
});
} finally {
setSubmitting(false);
}
};
if (!open) return null;
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000
}} onClick={onClose}>
<div style={{
background: '#fff',
borderRadius: '8px',
padding: '24px',
width: '520px',
maxWidth: '90%',
maxHeight: '90vh',
overflow: 'auto'
}} onClick={e => e.stopPropagation()}>
<h2 style={{ margin: '0 0 20px 0' }}>Workspace</h2>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="请输入标题"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}> ()</label>
<input
type="text"
value={tags}
onChange={e => setTags(e.target.value)}
placeholder="标签1, 标签2, 标签3"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={link}
onChange={e => setLink(e.target.value)}
placeholder="https://..."
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<input
type="text"
value={summary}
onChange={e => setSummary(e.target.value)}
placeholder="简要描述"
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}></label>
<textarea
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="详细描述..."
rows={3}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box',
resize: 'vertical'
}}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontWeight: 500 }}> (JSON)</label>
<textarea
value={data}
onChange={e => setData(e.target.value)}
placeholder='{"key": "value"}'
rows={4}
style={{
width: '100%',
padding: '8px 12px',
border: '1px solid #ddd',
borderRadius: '4px',
boxSizing: 'border-box',
fontFamily: 'monospace',
resize: 'vertical'
}}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
<button
onClick={onClose}
style={{
padding: '8px 16px',
background: '#fff',
color: '#666',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer'
}}
>
</button>
<button
onClick={handleSubmit}
disabled={submitting}
style={{
padding: '8px 16px',
background: '#0070f3',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: submitting ? 'not-allowed' : 'pointer',
opacity: submitting ? 0.7 : 1
}}
>
{submitting ? '保存中...' : '保存'}
</button>
</div>
</div>
</div>
);
}
export default App;

View File

@@ -12,9 +12,8 @@ type WorkspaceItem = {
link?: string; link?: string;
summary?: string; summary?: string;
description?: string; description?: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
data?: any;
} }
type WorkspaceState = { type WorkspaceState = {
@@ -32,8 +31,8 @@ type WorkspaceState = {
setEditingItem: (item: WorkspaceItem | null) => void; setEditingItem: (item: WorkspaceItem | null) => void;
// 数据操作 // 数据操作
getList: (params?: { search?: string, page?: number, pageSize?: number }) => Promise<void>; getList: (params?: { search?: string, page?: number, pageSize?: number }) => Promise<void>;
createItem: (data: { title: string, tags?: string[], link?: string, summary?: string, description?: string, data?: any }) => Promise<void>; createItem: (data: { title: string, tags?: string[], link?: string, summary?: string, description?: string }) => Promise<void>;
updateItem: (id: string, data: { title?: string, tags?: string[], link?: string, summary?: string, description?: string, data?: any }) => Promise<void>; updateItem: (id: string, data: { title?: string, tags?: string[], link?: string, summary?: string, description?: string }) => Promise<void>;
deleteItem: (id: string) => Promise<void>; deleteItem: (id: string) => Promise<void>;
getItem: (id: string) => Promise<WorkspaceItem | null>; getItem: (id: string) => Promise<WorkspaceItem | null>;
} }
@@ -85,8 +84,7 @@ export const useWorkspaceStore = create<WorkspaceState>((set, get) => ({
tags: data.tags || [], tags: data.tags || [],
link: data.link || '', link: data.link || '',
summary: data.summary || '', summary: data.summary || '',
description: data.description || '', description: data.description || ''
data: data.data || {}
}); });
if (res.code === 200) { if (res.code === 200) {
toast.success('创建成功'); toast.success('创建成功');