feat(web): 贴纸包 CRUD UI + 主题编辑/停用 — Task 14-15 完成
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

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:
iven
2026-06-02 23:40:46 +08:00
parent f0741450bc
commit d6dd017155
7 changed files with 337 additions and 18 deletions

View File

@@ -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>
);
}