feat(admin): batch upload for package, store env, and product images
Allow multi-select uploads in HQ, partner, and shop for the three image galleries. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,7 +34,7 @@ function mapToForm(d: Record<string, unknown>) {
|
||||
const row = d as Row;
|
||||
return {
|
||||
...row,
|
||||
detailImageUrls: row.detailImageUrls?.length ? row.detailImageUrls : [''],
|
||||
detailImageUrls: row.detailImageUrls?.length ? row.detailImageUrls : [],
|
||||
features: row.features?.length
|
||||
? row.features
|
||||
: [{ icon: 'star', title: '', desc: '' }],
|
||||
@@ -68,12 +68,14 @@ function buildPayload(v: TemplateFormValues) {
|
||||
function TemplateContentFields() {
|
||||
return (
|
||||
<>
|
||||
<Typography.Text type="secondary">模板详情长图(套用商品时可逐张修改)</Typography.Text>
|
||||
<DetailImageUrlList
|
||||
label="详情图"
|
||||
bizType="DETAIL_TEMPLATE"
|
||||
maxCount={TEMPLATE_MAX_DETAIL_IMAGES}
|
||||
/>
|
||||
<Typography.Text type="secondary">模板详情长图(套用商品时可逐张修改,支持批量上传)</Typography.Text>
|
||||
<Form.Item name="detailImageUrls" style={{ marginBottom: 0 }}>
|
||||
<DetailImageUrlList
|
||||
label="详情图"
|
||||
bizType="DETAIL_TEMPLATE"
|
||||
maxCount={TEMPLATE_MAX_DETAIL_IMAGES}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Form.Item name="storyTitle" label="故事标题">
|
||||
<Input placeholder="如:千年杜康 · 唯有此处" />
|
||||
@@ -228,7 +230,7 @@ export default function ProductDetailTemplatesPage() {
|
||||
}} width={640}>
|
||||
<Form form={createForm} layout="vertical" initialValues={{
|
||||
status: 'ACTIVE', sortOrder: 0,
|
||||
detailImageUrls: [''],
|
||||
detailImageUrls: [],
|
||||
features: [{ icon: 'water_drop', title: '', desc: '' }],
|
||||
}}>
|
||||
<Form.Item name="code" label="编码" rules={[{ required: true }]} extra="唯一标识,如 dukang-classic">
|
||||
|
||||
@@ -9,6 +9,7 @@ import { request } from '../lib/api';
|
||||
import { AROMA_TYPE_LABELS, PRODUCT_STATUS_LABELS, fmtTime } from '../lib/constants';
|
||||
import { useAdminList } from '../lib/useAdminList';
|
||||
import OssUpload from '../components/OssUpload';
|
||||
import MultiImageUpload from '../components/MultiImageUpload';
|
||||
import DetailImageUrlList from '../components/DetailImageUrlList';
|
||||
import ProductDetailTemplatePicker from '../components/ProductDetailTemplatePicker';
|
||||
import type { FormInstance } from 'antd/es/form';
|
||||
@@ -73,8 +74,8 @@ function mapDetailToForm(d: Record<string, unknown>) {
|
||||
return {
|
||||
...d,
|
||||
coverUrl: (d as { mainImageUrl?: string }).mainImageUrl,
|
||||
carouselUrls: (row.carouselUrls?.length ? row.carouselUrls : ['']) as string[],
|
||||
detailImageUrls: (row.detailImageUrls?.length ? row.detailImageUrls : ['']) as string[],
|
||||
carouselUrls: (row.carouselUrls?.length ? row.carouselUrls : []) as string[],
|
||||
detailImageUrls: (row.detailImageUrls?.length ? row.detailImageUrls : []) as string[],
|
||||
allowOnlinePurchase: row.allowOnlinePurchase !== false,
|
||||
allowCrossCityDelivery: row.allowOnlinePurchase === false ? false : row.allowCrossCityDelivery !== false,
|
||||
allowOnSitePickup: !!row.allowOnSitePickup,
|
||||
@@ -129,25 +130,13 @@ function buildProductPayload(v: ProductFormValues) {
|
||||
|
||||
function ImageUrlList({ name, label, bizType }: { name: string; label: string; bizType: string }) {
|
||||
return (
|
||||
<Form.List name={name}>
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map((field) => (
|
||||
<Space key={field.key} align="start" style={{ display: 'flex', marginBottom: 8 }}>
|
||||
<Form.Item {...field} style={{ flex: 1, marginBottom: 0 }}>
|
||||
<OssUpload bizType={bizType} mediaType="IMAGE" />
|
||||
</Form.Item>
|
||||
{fields.length > 1 && (
|
||||
<MinusCircleOutlined onClick={() => remove(field.name)} style={{ marginTop: 8 }} />
|
||||
)}
|
||||
</Space>
|
||||
))}
|
||||
<Button type="dashed" onClick={() => add('')} block icon={<PlusOutlined />}>
|
||||
添加{label}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
<Form.Item name={name} style={{ marginBottom: 0 }}>
|
||||
<MultiImageUpload
|
||||
bizType={bizType}
|
||||
mediaType="IMAGE"
|
||||
tip={`${label}支持一次选择多张批量上传`}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -156,11 +145,13 @@ function ProductDetailFields({ form, aromaType }: { form: FormInstance; aromaTyp
|
||||
<>
|
||||
<ProductDetailTemplatePicker form={form} aromaType={aromaType} />
|
||||
<Divider />
|
||||
<Typography.Text type="secondary">详情页轮播(CAROUSEL,单张最大 10MB)</Typography.Text>
|
||||
<Typography.Text type="secondary">详情页轮播(CAROUSEL,支持批量上传,单张最大 10MB)</Typography.Text>
|
||||
<ImageUrlList name="carouselUrls" label="轮播图" bizType="CAROUSEL" />
|
||||
<Divider />
|
||||
<Typography.Text type="secondary">详情长图(DETAIL,可逐张修改,单张最大 10MB)</Typography.Text>
|
||||
<DetailImageUrlList label="详情图" bizType="DETAIL" />
|
||||
<Typography.Text type="secondary">详情长图(DETAIL,支持批量上传,单张最大 10MB)</Typography.Text>
|
||||
<Form.Item name="detailImageUrls" style={{ marginBottom: 0 }}>
|
||||
<DetailImageUrlList label="详情图" bizType="DETAIL" />
|
||||
</Form.Item>
|
||||
<Divider />
|
||||
<Form.Item name="storyTitle" label="故事标题">
|
||||
<Input placeholder="如:千年杜康 · 唯有此处" />
|
||||
@@ -468,7 +459,7 @@ export default function ProductsPage() {
|
||||
aromaType: 'QINGXIANG', status: 'DRAFT', sortOrder: 0,
|
||||
allowOnlinePurchase: true, allowCrossCityDelivery: true, allowOnSitePickup: false,
|
||||
visibilityWhitelistEnabled: false, visibilityPhones: [],
|
||||
carouselUrls: [''], detailImageUrls: [''],
|
||||
carouselUrls: [], detailImageUrls: [],
|
||||
features: [{ icon: 'water_drop', title: '', desc: '' }],
|
||||
}}>
|
||||
<Tabs items={[
|
||||
|
||||
@@ -40,6 +40,7 @@ import { useAdminList } from '../lib/useAdminList';
|
||||
import { resolveRegionBinding } from '../lib/china-region';
|
||||
import ChinaRegionCascader from '../components/ChinaRegionCascader';
|
||||
import OssUpload from '../components/OssUpload';
|
||||
import MultiImageUpload from '../components/MultiImageUpload';
|
||||
import TencentLocPickerModal from '../components/TencentLocPickerModal';
|
||||
import AdminStorePackagesSection from '../components/AdminStorePackagesSection';
|
||||
|
||||
@@ -118,42 +119,18 @@ function StoreAuditMediaEditor() {
|
||||
<Form.Item name="coverUrl" label="门头照">
|
||||
<OssUpload bizType="STORE_TITLE" mediaType="IMAGE" />
|
||||
</Form.Item>
|
||||
<Typography.Text strong>环境照片</Typography.Text>
|
||||
<Typography.Paragraph type="secondary" style={{ marginTop: 4 }}>
|
||||
建议至少 3 张;可替换、删除,最多 20 张
|
||||
</Typography.Paragraph>
|
||||
<Form.List name="envPhotoUrls">
|
||||
{(fields, { add, remove }) => (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.key} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
||||
<Form.Item
|
||||
name={field.name}
|
||||
label={`环境图 ${index + 1}`}
|
||||
style={{ flex: 1, marginBottom: 12 }}
|
||||
>
|
||||
<OssUpload bizType="STORE_ENV" mediaType="IMAGE" />
|
||||
</Form.Item>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
style={{ marginTop: 30 }}
|
||||
onClick={() => remove(field.name)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{fields.length < 20 ? (
|
||||
<Button type="dashed" onClick={() => add('')} block>
|
||||
添加环境照片
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">已达上限 20 张</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
<Form.Item
|
||||
name="envPhotoUrls"
|
||||
label="环境照片"
|
||||
extra="建议至少 3 张;支持批量上传,最多 20 张。可逐张删除后保存。"
|
||||
>
|
||||
<MultiImageUpload
|
||||
bizType="STORE_ENV"
|
||||
mediaType="IMAGE"
|
||||
maxCount={20}
|
||||
tip="环境照支持一次选择多张批量上传"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="contractUrl" label="签约合同" style={{ marginTop: 16 }}>
|
||||
<OssUpload bizType="STORE_CONTRACT" mediaType="FILE" accept="image/*,.pdf" />
|
||||
</Form.Item>
|
||||
@@ -404,7 +381,7 @@ export default function StoresPage() {
|
||||
envPhotoUrls: (() => {
|
||||
const { envs } = collectMediaUrls(d);
|
||||
const urls = envs.map((item) => item.url).filter(Boolean);
|
||||
return urls.length ? urls : [''];
|
||||
return urls;
|
||||
})(),
|
||||
contractUrl: (() => {
|
||||
const { contracts } = collectMediaUrls(d);
|
||||
@@ -559,7 +536,7 @@ export default function StoresPage() {
|
||||
function openCreateModal() {
|
||||
void loadOptions();
|
||||
createForm.setFieldsValue({
|
||||
envPhotoUrls: ['', '', ''],
|
||||
envPhotoUrls: [],
|
||||
settlementRate: 60,
|
||||
openTime: '10:00',
|
||||
closeTime: '22:00',
|
||||
@@ -1261,33 +1238,18 @@ export default function StoresPage() {
|
||||
<Form.Item name="coverUrl" label="门头照">
|
||||
<OssUpload bizType="STORE_TITLE" mediaType="IMAGE" />
|
||||
</Form.Item>
|
||||
<Typography.Text strong>环境照片</Typography.Text>
|
||||
<Typography.Paragraph type="secondary" style={{ marginTop: 4 }}>
|
||||
至少 3 张,可继续添加,最多 20 张
|
||||
</Typography.Paragraph>
|
||||
<Form.List name="envPhotoUrls">
|
||||
{(fields, { add, remove }) => (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.key} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
|
||||
<Form.Item key={field.key} name={field.name} label={`环境图 ${index + 1}`} style={{ flex: 1 }}>
|
||||
<OssUpload bizType="STORE_ENV" mediaType="IMAGE" />
|
||||
</Form.Item>
|
||||
<Button type="link" danger style={{ marginTop: 30 }} onClick={() => remove(field.name)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{fields.length < 20 ? (
|
||||
<Button type="dashed" onClick={() => add('')} block>
|
||||
添加环境照片
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">已达上限 20 张</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
<Form.Item
|
||||
name="envPhotoUrls"
|
||||
label="环境照片"
|
||||
extra="选填;支持批量上传,最多 20 张"
|
||||
>
|
||||
<MultiImageUpload
|
||||
bizType="STORE_ENV"
|
||||
mediaType="IMAGE"
|
||||
maxCount={20}
|
||||
tip="环境照支持一次选择多张批量上传"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="contractUrl" label="签约合同">
|
||||
<OssUpload bizType="STORE_CONTRACT" mediaType="FILE" accept="image/*,.pdf" />
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user