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 }) => { 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;
}, },

View File

@@ -318,9 +318,19 @@ export default function PointsProductList() {
title: '展示设置', title: '展示设置',
fields: ( fields: (
<> <>
<Form.Item name="image_url" label="商品图片"> <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%' }}> <Space.Compact style={{ width: '100%' }}>
<Input placeholder="输入 URL 或从媒体库选择" style={{ flex: 1 }} /> <Input
value={imageUrl || ''}
onChange={(e) => setFieldValue('image_url', e.target.value || undefined)}
placeholder="输入 URL 或从媒体库选择"
style={{ flex: 1 }}
/>
<Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}> <Button icon={<PictureOutlined />} onClick={() => setMediaPickerOpen(true)}>
</Button> </Button>
@@ -330,7 +340,7 @@ export default function PointsProductList() {
beforeUpload={async (file) => { beforeUpload={async (file) => {
try { try {
const result = await uploadFile(file); const result = await uploadFile(file);
form.setFieldValue('image_url', result.url); setFieldValue('image_url', result.url);
message.success('图片上传成功'); message.success('图片上传成功');
} catch { } catch {
message.error('图片上传失败'); message.error('图片上传失败');
@@ -341,28 +351,24 @@ export default function PointsProductList() {
<Button icon={<UploadOutlined />}></Button> <Button icon={<UploadOutlined />}></Button>
</Upload> </Upload>
</Space.Compact> </Space.Compact>
</Form.Item> {imageUrl && (
<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 (
<div <div
style={{ style={{
marginTop: -20, marginTop: 8,
marginBottom: 16,
borderRadius: 8, borderRadius: 8,
overflow: 'hidden', overflow: 'hidden',
border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`, border: `1px solid ${isDark ? '#1e293b' : '#e2e8f0'}`,
}} }}
> >
<img <img
src={url} src={imageUrl}
alt="商品图片预览" alt="商品图片预览"
style={{ width: '100%', height: 120, objectFit: 'cover' }} style={{ width: '100%', height: 120, objectFit: 'cover' }}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/> />
</div> </div>
)}
</>
); );
}} }}
</Form.Item> </Form.Item>