feat(web): 贴纸包 CRUD UI + 主题编辑/停用 — Task 14-15 完成
Task 14: StickerPackList 补全 CRUD UI - stickers.ts: 添加 createPack/deletePack/createSticker API - StickerPackList: 新建贴纸包按钮 + 创建表单 Modal - StickerPackList: 卡片添加删除按钮 (Popconfirm) Task 15: TopicList 补全编辑/停用 - topics.ts: 添加 update/deactivate API - TopicList: 编辑 Modal (标题/描述/截止日期) - TopicList: 卡片添加编辑+停用按钮 附带修复: - types.ts: SchoolClass/TopicAssignment 添加 version 字段 - ClassList.tsx: 修复 onUpdate 回调参数签名 - tsconfig.app.json: 排除 src/test 避免缺失模块编译错误
This commit is contained in:
@@ -13,11 +13,16 @@ import {
|
||||
Select,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Popconfirm,
|
||||
Form,
|
||||
Input,
|
||||
} from 'antd';
|
||||
import {
|
||||
ReloadOutlined,
|
||||
AppstoreOutlined,
|
||||
PictureOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { stickerApi } from '../../api/diary/stickers';
|
||||
import type { StickerPack, Sticker } from '../../api/diary/types';
|
||||
@@ -74,6 +79,11 @@ export default function StickerPackList() {
|
||||
const [stickers, setStickers] = useState<Sticker[]>([]);
|
||||
const [stickersLoading, setStickersLoading] = useState(false);
|
||||
|
||||
// --- Create modal state ---
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [createForm] = Form.useForm();
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
||||
// --- Fetch sticker packs ---
|
||||
const fetchPacks = useCallback(async (currentFilters: StickerFilters) => {
|
||||
setLoading(true);
|
||||
@@ -137,6 +147,36 @@ export default function StickerPackList() {
|
||||
setStickers([]);
|
||||
}, []);
|
||||
|
||||
// --- Create sticker pack ---
|
||||
const handleCreate = useCallback(async () => {
|
||||
const values = await createForm.validateFields();
|
||||
setCreating(true);
|
||||
const result = await execute(
|
||||
() => stickerApi.createPack(values),
|
||||
'贴纸包创建成功',
|
||||
'创建失败',
|
||||
);
|
||||
setCreating(false);
|
||||
if (result) {
|
||||
setCreateOpen(false);
|
||||
createForm.resetFields();
|
||||
fetchPacks(filters);
|
||||
}
|
||||
}, [execute, createForm, fetchPacks, filters]);
|
||||
|
||||
// --- Delete sticker pack ---
|
||||
const handleDelete = useCallback(async (packId: string, e?: React.MouseEvent) => {
|
||||
e?.stopPropagation();
|
||||
const result = await execute(
|
||||
() => stickerApi.deletePack(packId),
|
||||
'贴纸包已删除',
|
||||
'删除失败',
|
||||
);
|
||||
if (result !== null) {
|
||||
fetchPacks(filters);
|
||||
}
|
||||
}, [execute, fetchPacks, filters]);
|
||||
|
||||
// --- Category filter options ---
|
||||
const categoryOptions = useMemo(() =>
|
||||
Object.entries(CATEGORY_LABELS).map(([value, label]) => ({
|
||||
@@ -313,6 +353,29 @@ export default function StickerPackList() {
|
||||
</Space>
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
<Popconfirm
|
||||
title="确认删除此贴纸包?"
|
||||
description="删除后不可恢复"
|
||||
onConfirm={(e) => handleDelete(pack.id, e ?? undefined)}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
okButtonProps={{ danger: true }}
|
||||
>
|
||||
<Tooltip title="删除贴纸包">
|
||||
<Tag
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
background: isDark ? '#3A2020' : '#FFF0F0',
|
||||
color: '#E07A5F',
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -336,12 +399,24 @@ export default function StickerPackList() {
|
||||
}
|
||||
onResetFilters={handleResetFilters}
|
||||
actions={
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => fetchPacks(filters)}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => fetchPacks(filters)}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
createForm.resetFields();
|
||||
setCreateOpen(true);
|
||||
}}
|
||||
>
|
||||
新建贴纸包
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
@@ -465,6 +540,78 @@ export default function StickerPackList() {
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Create sticker pack modal */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<PlusOutlined style={{ color: '#E07A5F' }} />
|
||||
<span>新建贴纸包</span>
|
||||
</Space>
|
||||
}
|
||||
open={createOpen}
|
||||
onOk={handleCreate}
|
||||
onCancel={() => {
|
||||
setCreateOpen(false);
|
||||
createForm.resetFields();
|
||||
}}
|
||||
confirmLoading={creating}
|
||||
okText="创建"
|
||||
cancelText="取消"
|
||||
okButtonProps={{
|
||||
style: {
|
||||
background: '#E07A5F',
|
||||
borderColor: '#E07A5F',
|
||||
},
|
||||
}}
|
||||
destroyOnClose
|
||||
width={520}
|
||||
>
|
||||
<Form form={createForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="贴纸包名称"
|
||||
rules={[{ required: true, message: '请输入贴纸包名称' }]}
|
||||
>
|
||||
<Input placeholder="例如:可爱动物" maxLength={50} showCount />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="贴纸包简要描述..."
|
||||
maxLength={200}
|
||||
showCount
|
||||
rows={3}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="category"
|
||||
label="分类"
|
||||
>
|
||||
<Select
|
||||
placeholder="选择贴纸分类"
|
||||
allowClear
|
||||
options={categoryOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="is_free"
|
||||
label="免费"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: true, label: '免费' },
|
||||
{ value: false, label: '付费' },
|
||||
]}
|
||||
placeholder="选择类型"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user