fix(web): 积分商品图片预览 — 改用 React state 驱动替代 antd Form shouldUpdate

ant Form shouldUpdate + setFieldValue 在 DrawerForm 上下文中无法正确触发重渲染,
改用独立 imageUrl state 管理,Input/预览/MediaPicker/Upload 全部通过 state 同步
This commit is contained in:
iven
2026-05-26 10:30:22 +08:00
parent 42299a6722
commit 9d6a92e1d7

View File

@@ -66,7 +66,7 @@ export default function PointsProductList() {
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<PointsProduct | null>(null);
const [mediaPickerOpen, setMediaPickerOpen] = useState(false);
const [form] = Form.useForm();
const [imageUrl, setImageUrl] = useState<string>('');
const isDark = useThemeMode();
const fetchProducts = useCallback(
@@ -103,11 +103,13 @@ export default function PointsProductList() {
// ---- 新建 / 编辑 ----
const openCreate = () => {
setEditing(null);
setImageUrl('');
setModalOpen(true);
};
const openEdit = (record: PointsProduct) => {
setEditing(record);
setImageUrl(record.image_url || '');
setModalOpen(true);
};
@@ -124,12 +126,12 @@ export default function PointsProductList() {
points_cost: number;
stock: number;
description?: string;
image_url?: string;
sort_order?: number;
};
if (editing) {
await pointsApi.updateProduct(editing.id, {
...typed,
image_url: imageUrl || undefined,
version: editing.version,
});
} else {
@@ -139,7 +141,7 @@ export default function PointsProductList() {
points_cost: typed.points_cost,
stock: typed.stock,
description: typed.description,
image_url: typed.image_url,
image_url: imageUrl || undefined,
sort_order: typed.sort_order,
};
await pointsApi.createProduct(req);
@@ -318,59 +320,51 @@ export default function PointsProductList() {
title: '展示设置',
fields: (
<>
<Form.Item name="image_url" hidden><Input /></Form.Item>
<Form.Item label="商品图片" shouldUpdate={(prev, cur) => prev.image_url !== cur.image_url}>
{({ getFieldValue, setFieldValue }) => {
const imageUrl: string | undefined = getFieldValue('image_url');
return (
<>
<Space.Compact style={{ width: '100%' }}>
<Input
value={imageUrl || ''}
onChange={(e) => setFieldValue('image_url', e.target.value || undefined)}
placeholder="输入 URL 或从媒体库选择"
style={{ flex: 1 }}
/>
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
</Button>
<Upload
accept="image/*"
showUploadList={false}
beforeUpload={async (file) => {
try {
const result = await uploadFile(file);
setFieldValue('image_url', result.url);
message.success('图片上传成功');
} catch {
message.error('图片上传失败');
}
return false;
}}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Space.Compact>
{imageUrl && (
<div
style={{
marginTop: 8,
borderRadius: 8,
overflow: 'hidden',
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
}}
>
<img
src={imageUrl}
alt="商品图片预览"
style={{ width: '100%', height: 120, objectFit: 'cover' }}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
</div>
)}
</>
);
}}
<Form.Item label="商品图片">
<Space.Compact style={{ width: '100%' }}>
<Input
value={imageUrl}
onChange={(e) => setImageUrl(e.target.value)}
placeholder="输入 URL 或从媒体库选择"
style={{ flex: 1 }}
/>
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
</Button>
<Upload
accept="image/*"
showUploadList={false}
beforeUpload={async (file) => {
try {
const result = await uploadFile(file);
setImageUrl(result.url);
message.success('图片上传成功');
} catch {
message.error('图片上传失败');
}
return false;
}}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Space.Compact>
{imageUrl && (
<div
style={{
marginTop: 8,
borderRadius: 8,
overflow: 'hidden',
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
}}
>
<img
src={imageUrl}
alt="商品图片预览"
style={{ width: '100%', height: 120, objectFit: 'cover' }}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
</div>
)}
</Form.Item>
<Form.Item name="sort_order" label="排序">
<InputNumber min={0} max={9999} style={{ width: '100%' }} placeholder="0" />
@@ -454,7 +448,6 @@ export default function PointsProductList() {
points_cost: editing.points_cost,
stock: editing.stock,
description: editing.description,
image_url: editing.image_url,
sort_order: editing.sort_order,
}
: { stock: -1, sort_order: 0 }}
@@ -462,14 +455,13 @@ export default function PointsProductList() {
width={600}
columns={2}
sections={formSections}
form={form}
/>
<MediaPicker
open={mediaPickerOpen}
onClose={() => setMediaPickerOpen(false)}
onSelect={(url) => {
form.setFieldValue('image_url', url);
setImageUrl(url);
message.success('已选择图片');
}}
/>