diff --git a/backend/view.py b/backend/view.py index c14cec6..e70bb55 100644 --- a/backend/view.py +++ b/backend/view.py @@ -44,6 +44,7 @@ from .views.products import ( from .views.members import ( GetMemberListView, UpdateMemberView, AddMemberView, ClubMemberCatalogView, ClubMemberEnableView, ClubMemberDisableView, + MemberCardImageUploadView, ) from .views.guanli import ( GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, @@ -102,6 +103,7 @@ __all__ = [ # members 'GetMemberListView', 'UpdateMemberView', 'AddMemberView', 'ClubMemberCatalogView', 'ClubMemberEnableView', 'ClubMemberDisableView', + 'MemberCardImageUploadView', # guanli 'GetGuanliListView', 'GetGuanliDetailView', 'PromoteGuanshiToZuzhangView', 'UpdateGuanliView', diff --git a/backend/views/members.py b/backend/views/members.py index 17130c5..bd78011 100644 --- a/backend/views/members.py +++ b/backend/views/members.py @@ -349,5 +349,27 @@ class ClubMemberDisableView(APIView): return Response({'code': 0, 'msg': '已下架', 'data': data}) +class MemberCardImageUploadView(APIView): + """POST /jituan/houtai/hy-card-upload 上传会员充值卡片图""" + permission_classes = [] + parser_classes = [MultiPartParser, FormParser, JSONParser] + + def post(self, request): + username = (request.data.get('username') or request.data.get('phone') or '').strip() + huiyuan_id = (request.data.get('huiyuan_id') or '').strip() + field = (request.data.get('field') or 'card_image').strip() + file_obj = request.FILES.get('file') + if not username or not huiyuan_id or not file_obj: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, username) + if kefu is None: + return permissions + if '3300a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_member_admin import upload_member_card_image + data, err = upload_member_card_image(request, huiyuan_id, field, file_obj) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '上传成功', 'data': data}) diff --git a/jituan/services/club_member_admin.py b/jituan/services/club_member_admin.py index 8175dd1..4adc402 100644 --- a/jituan/services/club_member_admin.py +++ b/jituan/services/club_member_admin.py @@ -228,6 +228,38 @@ def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzha return {'huiyuan_id': huiyuan_id}, None +MEMBER_CARD_FOLDER = 'beijing/huiyuan' + + +def upload_member_card_image(request, huiyuan_id, field, file_obj): + """上传会员充值页卡片图,写入 club_huiyuan_price.card_image / card_bg。""" + import os + from utils.oss_utils import validate_image, upload_to_oss + + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + if field not in ('card_image', 'card_bg'): + return None, '无效的图字段' + ok, msg = validate_image(file_obj) + if not ok: + return None, msg + + club_id = club_id_for_write(request) + ext = os.path.splitext(getattr(file_obj, 'name', '') or '')[1].lower() + if ext not in ('.png', '.jpg', '.jpeg', '.webp', '.gif'): + ext = '.png' + rel_path = f'{MEMBER_CARD_FOLDER}/{club_id}/{huiyuan_id}_{field}{ext}' + if not upload_to_oss(file_obj, rel_path): + return None, '上传 OSS 失败' + + row = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id).first() + if not row: + return None, '请先在当前俱乐部上架该会员' + setattr(row, field, rel_path) + row.save(update_fields=[field, 'UpdateTime']) + return {field: rel_path, 'huiyuan_id': huiyuan_id}, None + + def enable_member_for_club(request, huiyuan_id, jiage, guanshifc, zuzhangfc, extra=None): """俱乐部上架集团会员类型。""" if club_write_blocked(request): diff --git a/jituan/services/miniapp_assets.py b/jituan/services/miniapp_assets.py index 5cbb0d0..b73c5b8 100644 --- a/jituan/services/miniapp_assets.py +++ b/jituan/services/miniapp_assets.py @@ -29,6 +29,49 @@ MINIAPP_ICON_META = [ ('fighter_recharge_vip_bg', '打手会员充值页-顶部背景图'), ('fighter_deposit_bg', '打手保证金页-顶部背景图'), ('fighter_deposit_hero', '打手保证金页-卡片右侧装饰图'), + ('fighter_deposit_benefit_1', '保证金页-权益图标1'), + ('fighter_deposit_benefit_2', '保证金页-权益图标2'), + ('fighter_deposit_benefit_3', '保证金页-权益图标3'), + ('fighter_deposit_benefit_4', '保证金页-权益图标4'), + ('fighter_recharge_benefit_1', '会员充值页-权益图标1'), + ('fighter_recharge_benefit_2', '会员充值页-权益图标2'), + ('fighter_recharge_benefit_3', '会员充值页-权益图标3'), + ('fighter_recharge_advantage_1', '会员充值页-平台优势1'), + ('fighter_recharge_advantage_2', '会员充值页-平台优势2'), + ('fighter_recharge_advantage_3', '会员充值页-平台优势3'), + ('fighter_recharge_advantage_4', '会员充值页-平台优势4'), + ('fighter_order_pending', '打手中心-订单-待完成'), + ('fighter_order_settling', '打手中心-订单-待结算'), + ('fighter_order_done', '打手中心-订单-已完成'), + ('fighter_order_closed', '打手中心-订单-已关闭'), + ('fighter_trade_commission', '打手中心-佣金记录'), + ('fighter_trade_withdraw', '打手中心-提现记录'), + ('fighter_trade_settle', '打手中心-结算记录'), + ('fighter_trade_deduct', '打手中心-扣款记录'), + ('fighter_trade_recharge', '打手中心-充值记录'), + ('fighter_invite_subordinate', '打手中心-我的下级'), + ('fighter_invite_poster', '打手中心-推广海报'), + ('fighter_invite_code', '打手中心-邀请码'), + ('fighter_kuaishou_cash', '打手中心-快手提现'), + ('fighter_mine_copy', '打手中心-复制ID'), + ('fighter_mine_edit', '打手中心-编辑资料'), + ('fighter_mine_qiepian', '打手中心-切片活动横幅'), + ('fighter_mine_penalty_tip', '打手中心-罚单提示图标'), + ('fighter_mine_kefu', '打手中心-在线客服'), + ('fighter_mine_shangjia', '打手中心-商家认证'), + ('fighter_mine_punish', '打手中心-我的处罚'), + ('fighter_mine_medal', '打手中心-考核金牌'), + ('fighter_mine_book', '打手中心-接单考试/规则'), + ('fighter_mine_rank', '打手中心-总排行榜'), + ('fighter_mine_invite', '打手中心-联系邀请人'), + ('fighter_mine_contact', '打手中心-联系组长'), + ('fighter_mine_record', '打手中心-会员/分红记录'), + ('fighter_mine_kaohe_dafen', '打手中心-考核打分'), + ('fighter_mine_kaohe_jilu', '打手中心-考核记录'), + ('fighter_mine_kaohe_zhongxin', '打手中心-考核中心'), + ('fighter_mine_dashou_auth', '打手中心-接单员认证'), + ('fighter_mine_clear', '打手中心-清除缓存'), + ('fighter_mine_switch', '打手中心-返回点单端'), ('mine_pindao', '频道入口'), ('icon_refresh', '通用刷新'), ('guanshi_poster_bg', '管事推广海报背景'), @@ -58,7 +101,39 @@ MINIAPP_ICON_DEFAULTS['fighter_recharge_deposit'] = f'{ICON_FOLDER}/fighter_rech MINIAPP_ICON_DEFAULTS['fighter_recharge_vip_bg'] = f'{ICON_FOLDER}/fighter_recharge_vip_bg.png' MINIAPP_ICON_DEFAULTS['fighter_deposit_bg'] = f'{ICON_FOLDER}/fighter_deposit_bg.png' MINIAPP_ICON_DEFAULTS['fighter_deposit_hero'] = f'{ICON_FOLDER}/fighter_deposit_hero.png' -MINIAPP_ICON_DEFAULTS['mine_pindao'] = 'beijing/tubiao/grzx_guanzhualong.jpg' +MINIAPP_ICON_DEFAULTS['fighter_order_pending'] = f'{ICON_FOLDER}/fighter_order_pending.png' +MINIAPP_ICON_DEFAULTS['fighter_order_settling'] = f'{ICON_FOLDER}/fighter_order_settling.png' +MINIAPP_ICON_DEFAULTS['fighter_order_done'] = f'{ICON_FOLDER}/fighter_order_done.png' +MINIAPP_ICON_DEFAULTS['fighter_order_closed'] = f'{ICON_FOLDER}/fighter_order_closed.png' +MINIAPP_ICON_DEFAULTS['fighter_trade_commission'] = f'{ICON_FOLDER}/fighter_trade_commission.png' +MINIAPP_ICON_DEFAULTS['fighter_trade_withdraw'] = f'{ICON_FOLDER}/fighter_trade_withdraw.png' +MINIAPP_ICON_DEFAULTS['fighter_trade_settle'] = f'{ICON_FOLDER}/fighter_trade_settle.png' +MINIAPP_ICON_DEFAULTS['fighter_trade_deduct'] = f'{ICON_FOLDER}/fighter_trade_deduct.png' +MINIAPP_ICON_DEFAULTS['fighter_trade_recharge'] = f'{ICON_FOLDER}/fighter_trade_recharge.png' +MINIAPP_ICON_DEFAULTS['fighter_invite_subordinate'] = f'{ICON_FOLDER}/fighter_invite_subordinate.png' +MINIAPP_ICON_DEFAULTS['fighter_invite_poster'] = f'{ICON_FOLDER}/fighter_invite_poster.png' +MINIAPP_ICON_DEFAULTS['fighter_invite_code'] = f'{ICON_FOLDER}/fighter_invite_code.png' +MINIAPP_ICON_DEFAULTS['fighter_kuaishou_cash'] = f'{ICON_FOLDER}/fighter_kuaishou_cash.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_copy'] = 'beijing/guanshiduan/icon-copy.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_edit'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-edit.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_qiepian'] = f'{ICON_FOLDER}/fighter_mine_qiepian.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_penalty_tip'] = f'{ICON_FOLDER}/fighter_mine_penalty_tip.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_kefu'] = 'beijing/tubiao/grzx_kefu.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_shangjia'] = 'beijing/tubiao/grzx_shangjia.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_punish'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-punish.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_medal'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-medal.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_book'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-book.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_rank'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-rank.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_invite'] = 'beijing/guanshiduan/icon-invite.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_contact'] = 'beijing/guanshiduan/icon-contact.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_record'] = 'beijing/guanshiduan/icon-record.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_kaohe_dafen'] = 'beijing/kaohe/daofen.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_kaohe_jilu'] = 'beijing/kaohe/jilu.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_kaohe_zhongxin'] = 'beijing/kaohe/zhongxin.png' +MINIAPP_ICON_DEFAULTS['fighter_mine_dashou_auth'] = 'beijing/tubiao/grzx_dashou.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_clear'] = 'beijing/tubiao/grzx_qingchu.jpg' +MINIAPP_ICON_DEFAULTS['fighter_mine_switch'] = f'{ICON_FOLDER}/fighter_mine_switch.png' +MINIAPP_ICON_DEFAULTS['mine_pindao'] = 'beijing/tubiao/grzx_pindao.jpg' MINIAPP_ICON_DEFAULTS['guanshi_poster_bg'] = 'beijing/haibaobeijing.jpg' MINIAPP_ICON_DEFAULTS['zuzhang_poster_bg'] = 'beijing/zuzhangbeijing.jpg' @@ -73,6 +148,13 @@ POSTER_BG_DEFAULTS = { POSTER_FOLDER = 'beijing/haibao' POSTER_ICON_KEYS = frozenset(POSTER_BG_FIELDS.values()) +# 仅后台上传后才下发给小程序(页面大背景等) +CONFIG_ONLY_DELIVERY_KEYS = frozenset([ + 'fighter_recharge_vip_bg', + 'fighter_deposit_bg', + 'fighter_deposit_hero', +]) + def default_icon_path(icon_key: str) -> str: return MINIAPP_ICON_DEFAULTS.get(icon_key) or f'{ICON_FOLDER}/{icon_key}.png' @@ -81,12 +163,23 @@ def default_icon_path(icon_key: str) -> str: def get_miniapp_icons_dict(club_id: str) -> dict: from config.models import ClubMiniappIcon - rows = ClubMiniappIcon.query.filter(club_id=club_id, is_active=True, is_uploaded=True) - return { - r.icon_key: (r.image_path or '').strip() - for r in rows - if r.icon_key and (r.image_path or '').strip() + rows = { + r.icon_key: r + for r in ClubMiniappIcon.query.filter(club_id=club_id, is_active=True) } + result = {} + for key, _label in MINIAPP_ICON_META: + row = rows.get(key) + if row and row.is_uploaded and (row.image_path or '').strip(): + result[key] = row.image_path.strip() + continue + if key in CONFIG_ONLY_DELIVERY_KEYS: + continue + if row and (row.image_path or '').strip(): + result[key] = row.image_path.strip() + else: + result[key] = default_icon_path(key) + return result def get_pindao_payload(club_id: str) -> dict: diff --git a/jituan/urls.py b/jituan/urls.py index b4da7d2..e405e34 100644 --- a/jituan/urls.py +++ b/jituan/urls.py @@ -22,6 +22,7 @@ from backend.view import ( PopupNoticeModifyAPIView, ShgxgsjView, UpdateMemberView, + MemberCardImageUploadView, UpdateWithdrawSettingsView, ) from users.views import KefuPunishmentListView @@ -138,6 +139,7 @@ urlpatterns = [ path('houtai/hthycatalog', ClubMemberCatalogView.as_view(), name='jituan_member_catalog'), path('houtai/hthysj', ClubMemberEnableView.as_view(), name='jituan_member_enable'), path('houtai/hthyxj', ClubMemberDisableView.as_view(), name='jituan_member_disable'), + path('houtai/hy-card-upload', MemberCardImageUploadView.as_view(), name='jituan_member_card_upload'), path('houtai/club-leixing-list', ClubShangpinLeixingListView.as_view(), name='jituan_club_leixing_list'), path('houtai/club-leixing-catalog', ClubShangpinLeixingCatalogView.as_view(), name='jituan_club_leixing_catalog'), path('houtai/club-leixing-enable', ClubShangpinLeixingEnableView.as_view(), name='jituan_club_leixing_enable'),