From 2ff5df09bc9d489f7c16cd42ad48d91bc9a8878a Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 28 Jul 2026 02:12:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=93=E5=8C=BA=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8E=E5=8F=B0=E4=B8=8A=E4=BC=A0=20OSS?= =?UTF-8?q?=EF=BC=8C=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=20tupian=5Furl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- backend/views/system.py | 67 ++++++++++++++++++++++++++++++--- products/views/product_query.py | 3 +- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/backend/views/system.py b/backend/views/system.py index a31f023..e2f643c 100644 --- a/backend/views/system.py +++ b/backend/views/system.py @@ -587,6 +587,24 @@ class ModifyProductTypeZoneView(APIView): else: return Response({'code': 400, 'msg': '无效的action'}) + def _upload_zhuanqu_icon(self, image_file): + """专区图标上传到 OSS,相对路径前缀 shangpin/zhuanqu/(按俱乐部存库,路径共用前缀)。""" + try: + valid, msg = validate_image(image_file) + if not valid: + return None, msg + except ImportError: + pass + ext = image_file.name.split('.')[-1].lower() if image_file.name and '.' in image_file.name else 'png' + if ext not in ('jpg', 'jpeg', 'png', 'gif', 'webp'): + ext = 'png' + new_filename = f"{int(time.time())}_{uuid.uuid4().hex[:12]}.{ext}" + oss_path = f"shangpin/zhuanqu/{new_filename}" + url = upload_to_oss(image_file, oss_path) + if not url: + return None, '图片上传失败' + return oss_path, None + def add_zhuanqu(self, request): mingzi = request.data.get('mingzi', '').strip() leixing_id = request.data.get('leixing_id') @@ -609,6 +627,16 @@ class ModifyProductTypeZoneView(APIView): except (TypeError, ValueError): return Response({'code': 400, 'msg': '审核状态无效(必须为1或2)'}) + # 图标:优先上传文件;否则可选手填相对路径(兼容旧用法) + tupian_url = (request.data.get('tupian_url') or '').strip() + image_file = request.FILES.get('file') + uploaded_path = None + if image_file: + uploaded_path, err = self._upload_zhuanqu_icon(image_file) + if err: + return Response({'code': 500 if err == '图片上传失败' else 400, 'msg': err}) + tupian_url = uploaded_path + try: with transaction.atomic(): from jituan.services.club_write import resolve_club_id_for_write @@ -619,11 +647,17 @@ class ModifyProductTypeZoneView(APIView): shenhezhuangtai=shenhezhuangtai, paixu=0, club_id=club_id, - tupian_url=(request.data.get('tupian_url') or '').strip(), + tupian_url=tupian_url, ) - return Response({'code': 0, 'msg': '添加成功', 'data': {'id': zhuanqu.id, 'club_id': club_id}}) + return Response({ + 'code': 0, + 'msg': '添加成功', + 'data': {'id': zhuanqu.id, 'club_id': club_id, 'tupian_url': tupian_url}, + }) except Exception as e: logger.error(f"添加专区失败: {e}", exc_info=True) + if uploaded_path: + delete_from_oss(uploaded_path) return Response({'code': 500, 'msg': '数据库错误'}) def update_zhuanqu(self, request): @@ -671,10 +705,18 @@ class ModifyProductTypeZoneView(APIView): except (TypeError, ValueError): return Response({'code': 400, 'msg': '审核状态无效(必须为1或2)'}) - if 'tupian_url' in request.data: + old_image_path = getattr(zhuanqu, 'tupian_url', None) or '' + image_file = request.FILES.get('file') + uploaded_path = None + if image_file: + uploaded_path, err = self._upload_zhuanqu_icon(image_file) + if err: + return Response({'code': 500 if err == '图片上传失败' else 400, 'msg': err}) + if uploaded_path != old_image_path: + changes['tupian_url'] = uploaded_path + elif 'tupian_url' in request.data: new_url = (request.data.get('tupian_url') or '').strip() - old_url = getattr(zhuanqu, 'tupian_url', None) or '' - if new_url != old_url: + if new_url != old_image_path: changes['tupian_url'] = new_url if not changes: @@ -685,9 +727,18 @@ class ModifyProductTypeZoneView(APIView): for field, value in changes.items(): setattr(zhuanqu, field, value) zhuanqu.save() - return Response({'code': 0, 'msg': '修改成功'}) + # 换图或清空后删旧 OSS(新上传路径与旧不同时) + if 'tupian_url' in changes and old_image_path and old_image_path != changes['tupian_url']: + delete_from_oss(old_image_path) + return Response({ + 'code': 0, + 'msg': '修改成功', + 'data': {'id': zhuanqu.id, 'tupian_url': getattr(zhuanqu, 'tupian_url', '') or ''}, + }) except Exception as e: logger.error(f"更新专区失败: {e}", exc_info=True) + if uploaded_path: + delete_from_oss(uploaded_path) return Response({'code': 500, 'msg': '修改失败'}) def delete_zhuanqu(self, request): @@ -710,6 +761,7 @@ class ModifyProductTypeZoneView(APIView): if zone_club != write_club: return Response({'code': 403, 'msg': '专区不属于当前俱乐部'}) + old_image_path = getattr(zhuanqu, 'tupian_url', None) or '' with transaction.atomic(): if delete_products: Shangpin.query.filter(zhuanqu_id=zone_id).delete() @@ -717,6 +769,9 @@ class ModifyProductTypeZoneView(APIView): Shangpin.query.filter(zhuanqu_id=zone_id).update(zhuanqu_id=None) zhuanqu.delete() + if old_image_path: + delete_from_oss(old_image_path) + return Response({'code': 0, 'msg': '删除成功'}) diff --git a/products/views/product_query.py b/products/views/product_query.py index 30dc898..eee7539 100644 --- a/products/views/product_query.py +++ b/products/views/product_query.py @@ -128,7 +128,8 @@ class ShangpinHuoquView(APIView): "id": zhuanqu.id, "mingzi": zhuanqu.mingzi, "leixing_id": zhuanqu.leixing_id, - "paixu": zhuanqu.paixu + "paixu": zhuanqu.paixu, + "tupian_url": getattr(zhuanqu, 'tupian_url', None) or '', }) # 3. 查询商品列表(只查询可见专区下的商品,并排序)