From 4d434c9c677ee6996b1bd0659b9577adcbfff470 Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Fri, 21 Aug 2026 17:36:53 +0800 Subject: [PATCH] =?UTF-8?q?v3.5.4=20=E8=A7=84=E6=A0=BC=E4=B8=BB=E5=9B=BE?= =?UTF-8?q?=E3=80=81=E9=97=A8=E5=BA=97=E4=BD=BF=E7=94=A8=E8=A7=84=E5=88=99?= =?UTF-8?q?=E4=B8=8E=E6=94=B6=E9=93=B6=E5=8F=B0=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../src/components/ProductSpecsEditor.tsx | 167 +++++++++++++++++- apps/admin-web/src/pages/ProductsPage.tsx | 2 +- apps/mini-user/src/assets/logo2.png | Bin 0 -> 70897 bytes apps/mini-user/src/pages/pay/index.tsx | 9 +- .../src/pages/product-detail/index.tsx | 11 +- .../src/pages/store-detail/index.tsx | 16 +- apps/mini-user/src/styles/order.css | 12 +- apps/mini-user/src/styles/product-detail.css | 3 +- apps/mini-user/src/styles/store-detail.css | 11 ++ docs/杜康好客-v3.5.4-开发文档.md | 71 ++++++-- packages/shared-types/src/catalog.ts | 3 + .../dukang-api/prisma/migrate-sku-image.sql | 3 + server/dukang-api/prisma/schema.prisma | 2 + .../src/modules/catalog/product-sku.util.ts | 1 + .../src/modules/ops/admin-products.service.ts | 6 +- .../src/modules/ops/dto/admin-mutate.dto.ts | 7 + 16 files changed, 276 insertions(+), 48 deletions(-) create mode 100644 apps/mini-user/src/assets/logo2.png create mode 100644 server/dukang-api/prisma/migrate-sku-image.sql diff --git a/apps/admin-web/src/components/ProductSpecsEditor.tsx b/apps/admin-web/src/components/ProductSpecsEditor.tsx index 60f7821..b89901b 100644 --- a/apps/admin-web/src/components/ProductSpecsEditor.tsx +++ b/apps/admin-web/src/components/ProductSpecsEditor.tsx @@ -1,10 +1,11 @@ import { useMemo, useState } from 'react'; import { - Button, Input, InputNumber, Radio, Select, Space, Switch, Table, Typography, message, + Button, Image, Input, InputNumber, Modal, Radio, Select, Space, Switch, Table, Typography, message, } from 'antd'; -import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; +import { EditOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { request } from '../lib/api'; import { PRODUCT_STATUS_LABELS } from '../lib/constants'; +import OssUpload from './OssUpload'; type SpecValue = { id?: string; name: string; sortOrder?: number }; type SpecAttr = { id?: string; name: string; sortOrder?: number; values: SpecValue[] }; @@ -24,6 +25,7 @@ type SkuRow = { isDefault: boolean; sortOrder?: number; specText?: string; + imageUrl?: string | null; }; function cartesian(attrs: SpecAttr[]): string[][] { @@ -73,6 +75,7 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku saleUnit: s.saleUnit === 'BOX' ? 'BOX' : 'BOTTLE', bottlesPerUnit: s.bottlesPerUnit || (s.saleUnit === 'BOX' ? 6 : 1), isDefault: !!s.isDefault, + imageUrl: s.imageUrl ?? '', })) : [ { @@ -86,10 +89,12 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku saleUnit: 'BOTTLE', bottlesPerUnit: 1, isDefault: true, + imageUrl: '', }, ], ); const [saving, setSaving] = useState(false); + const [editIndex, setEditIndex] = useState(null); const combos = useMemo(() => cartesian(attrs.filter((a) => a.name.trim() && a.values.some((v) => v.name.trim()))), [attrs]); @@ -111,12 +116,21 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku bottlesPerUnit: 1, isDefault: i === 0, sortOrder: i, + imageUrl: '', }; }); if (next.length && !next.some((s) => s.isDefault)) next[0].isDefault = true; setSkus(next.length ? next : skus); } + function patchSku(index: number, patch: Partial) { + setSkus((prev) => { + const next = [...prev]; + next[index] = { ...next[index], ...patch }; + return next; + }); + } + async function handleSave() { setSaving(true); try { @@ -168,6 +182,7 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku bottlesPerUnit: s.saleUnit === 'BOX' ? s.bottlesPerUnit || 6 : 1, isDefault: !!s.isDefault, sortOrder: i, + imageUrl: s.imageUrl?.trim() || null, })); const barcodes = payloadSkus.map((row) => row.barcode69); @@ -189,11 +204,13 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku } } + const editing = editIndex != null ? skus[editIndex] : null; + return (
- SKU 码由系统自动生成(DK 开头),无需填写。每个规格必须填写互不相同的 69 码。 - 先配置销售规格轴(如「包装」),再生成 SKU 矩阵;未配置规格时仅保留默认一行。 + SKU 码由系统自动生成(DK 开头),无需填写。每个规格必须填写互不相同的 69 码,并可单独上传主图。 + 点「填写」在弹窗中编辑。先配置销售规格轴(如「包装」),再生成 SKU 矩阵。 规格轴 @@ -446,12 +463,154 @@ export default function ProductSpecsEditor({ productId, initialAttrs, initialSku /> ), }, + { + title: '主图', + width: 72, + render: (_, row) => + row.imageUrl ? ( + + ) : ( + + ), + }, + { + title: '编辑', + width: 80, + fixed: 'right', + render: (_, row, index) => ( + + ), + }, ]} /> + + setEditIndex(null)} + onOk={() => setEditIndex(null)} + okText="完成" + width={560} + destroyOnClose + > + {editing && editIndex != null ? ( + +
+ SKU +
{editing.skuCode || '保存后自动生成'}
+
+
+ 69 码 * + patchSku(editIndex, { barcode69: e.target.value })} + /> +
+
+ 主图 + patchSku(editIndex, { imageUrl: url })} + /> +
+ +
+ 售价 + patchSku(editIndex, { price: Number(v) || 0 })} + /> +
+
+ 权益 + patchSku(editIndex, { benefitAmount: v == null ? undefined : Number(v) })} + /> +
+
+
+ 状态 + + patchSku(editIndex, { + saleUnit, + bottlesPerUnit: saleUnit === 'BOX' ? editing.bottlesPerUnit || 6 : 1, + }) + } + /> + {editing.saleUnit === 'BOX' ? ( + patchSku(editIndex, { bottlesPerUnit: Number(v) || 6 })} + /> + ) : null} + + + + patchSku(editIndex, { + allowOnlinePurchase: checked, + allowCrossCityDelivery: checked ? editing.allowCrossCityDelivery : false, + }) + } + /> + patchSku(editIndex, { allowCrossCityDelivery: checked })} + /> + patchSku(editIndex, { allowOnSitePickup: checked })} + /> + { + if (!checked) return; + setSkus((prev) => prev.map((s, i) => ({ ...s, isDefault: i === editIndex }))); + }} + /> + + + ) : null} +
); } diff --git a/apps/admin-web/src/pages/ProductsPage.tsx b/apps/admin-web/src/pages/ProductsPage.tsx index 51412b1..e02bcff 100644 --- a/apps/admin-web/src/pages/ProductsPage.tsx +++ b/apps/admin-web/src/pages/ProductsPage.tsx @@ -408,7 +408,7 @@ export default function ProductsPage() { { setPage(p); setPageSize(ps); } }} /> - setDrawerOpen(false)} + setDrawerOpen(false)} extra={detail && (