feat: 打手中心全量可配置图标下发与会员卡片图上传接口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-05 17:36:31 +08:00
parent 69c7ffd962
commit 5754a69e0b
5 changed files with 157 additions and 6 deletions

View File

@@ -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',

View File

@@ -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})

View File

@@ -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):

View File

@@ -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:

View File

@@ -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'),