fix(web): 积分商品图片选择器 + 更新 422 修复

- 图片字段改用 shouldUpdate 自定义渲染,值正确绑定到 Input 和预览
- updateProduct API 改为嵌套 { data, version } 格式,匹配后端 DTO
- 隐藏 Form.Item 保存 image_url 值到 form store
This commit is contained in:
iven
2026-05-26 10:12:04 +08:00
parent a2864713d6
commit 42299a6722
2 changed files with 52 additions and 45 deletions

View File

@@ -322,10 +322,11 @@ export const pointsApi = {
},
updateProduct: async (id: string, req: Partial<CreatePointsProductReq> & { is_active?: boolean; version: number }) => {
const { version, ...fields } = req;
const { data } = await client.put<{
success: boolean;
data: PointsProduct;
}>(`/health/admin/points/products/${id}`, req);
}>(`/health/admin/points/products/${id}`, { data: fields, version });
return data.data;
},

View File

@@ -318,51 +318,57 @@ export default function PointsProductList() {
title: '展示设置',
fields: (
<>
<Form.Item name="image_url" label="商品图片">
<Space.Compact style={{ width: '100%' }}>
<Input 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);
form.setFieldValue('image_url', result.url);
message.success('图片上传成功');
} catch {
message.error('图片上传失败');
}
return false;
}}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
</Space.Compact>
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.image_url !== cur.image_url}>
{({ getFieldValue }) => {
const url: string | undefined = getFieldValue('image_url');
if (!url) return null;
<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 (
<div
style={{
marginTop: -20,
marginBottom: 16,
borderRadius: 8,
overflow: 'hidden',
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
}}
>
<img
src={url}
alt="商品图片预览"
style={{ width: '100%', height: 120, objectFit: 'cover' }}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
</div>
<>
<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>