fix(web): 积分商品图片选择器 + 更新 422 修复
- 图片字段改用 shouldUpdate 自定义渲染,值正确绑定到 Input 和预览
- updateProduct API 改为嵌套 { data, version } 格式,匹配后端 DTO
- 隐藏 Form.Item 保存 image_url 值到 form store
This commit is contained in:
@@ -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;
|
||||
},
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user