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 }) => {
|
updateProduct: async (id: string, req: Partial<CreatePointsProductReq> & { is_active?: boolean; version: number }) => {
|
||||||
|
const { version, ...fields } = req;
|
||||||
const { data } = await client.put<{
|
const { data } = await client.put<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: PointsProduct;
|
data: PointsProduct;
|
||||||
}>(`/health/admin/points/products/${id}`, req);
|
}>(`/health/admin/points/products/${id}`, { data: fields, version });
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -318,51 +318,57 @@ export default function PointsProductList() {
|
|||||||
title: '展示设置',
|
title: '展示设置',
|
||||||
fields: (
|
fields: (
|
||||||
<>
|
<>
|
||||||
<Form.Item name="image_url" label="商品图片">
|
<Form.Item name="image_url" hidden><Input /></Form.Item>
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Form.Item label="商品图片" shouldUpdate={(prev, cur) => prev.image_url !== cur.image_url}>
|
||||||
<Input placeholder="输入 URL 或从媒体库选择" style={{ flex: 1 }} />
|
{({ getFieldValue, setFieldValue }) => {
|
||||||
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
|
const imageUrl: string | undefined = getFieldValue('image_url');
|
||||||
媒体库
|
|
||||||
</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;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
style={{
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
marginTop: -20,
|
<Input
|
||||||
marginBottom: 16,
|
value={imageUrl || ''}
|
||||||
borderRadius: 8,
|
onChange={(e) => setFieldValue('image_url', e.target.value || undefined)}
|
||||||
overflow: 'hidden',
|
placeholder="输入 URL 或从媒体库选择"
|
||||||
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
|
style={{ flex: 1 }}
|
||||||
}}
|
/>
|
||||||
>
|
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
|
||||||
<img
|
媒体库
|
||||||
src={url}
|
</Button>
|
||||||
alt="商品图片预览"
|
<Upload
|
||||||
style={{ width: '100%', height: 120, objectFit: 'cover' }}
|
accept="image/*"
|
||||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
showUploadList={false}
|
||||||
/>
|
beforeUpload={async (file) => {
|
||||||
</div>
|
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>
|
</Form.Item>
|
||||||
|
|||||||
Reference in New Issue
Block a user