fix(web): 积分商品图片预览 — 改用 React state 驱动替代 antd Form shouldUpdate
ant Form shouldUpdate + setFieldValue 在 DrawerForm 上下文中无法正确触发重渲染, 改用独立 imageUrl state 管理,Input/预览/MediaPicker/Upload 全部通过 state 同步
This commit is contained in:
@@ -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('已选择图片');
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user