"""小程序频道与可配置图标(按俱乐部)。""" from jituan.services.display_config import _display_club_id ICON_FOLDER = 'beijing/tubiao/chengxutubiao' # icon_key -> 后台展示名(文件名固定为 {icon_key}.png) MINIAPP_ICON_META = [ ('merchant_gold_banner', '商家金牌横幅(抢单/订单列表顶部)'), # 商家首页大图(与「我的」页小图标分开配置) ('merchant_home_regular', '商家首页-常规派单大图'), ('merchant_home_custom', '商家首页-自定义派单大图'), ('merchant_home_link', '商家首页-链接派单大图'), ('merchant_home_kefu_key', '商家首页-客服密钥'), ('merchant_home_kefu_list', '商家首页-客服列表'), ('merchant_home_notice', '商家首页-公告图标'), ('merchant_home_stat_bg', '商家首页-统计卡背景'), # 商家「我的」页功能小图标 ('merchant_regular_dispatch', '我的页-常规派单'), ('merchant_custom_dispatch', '我的页-自定义派单'), ('merchant_pending_settle', '我的页-待清算'), ('merchant_kefu_key', '我的页-客服密钥'), ('merchant_kefu_list', '我的页-客服列表'), ('merchant_link_dispatch', '我的页-链接派单'), ('merchant_punish', '我的页-处罚记录'), ('merchant_rank', '我的页-商家排行'), ('merchant_refresh', '我的页-刷新'), ('fighter_recharge_member', '打手中心-会员充值横幅(左侧)'), ('fighter_recharge_deposit', '打手中心-保证金充值横幅(右侧)'), ('fighter_recharge_vip_bg', '打手会员充值页-顶部背景图'), ('fighter_mine_vip_bg', '打手「我的」-VIP条背景图'), ('fighter_mine_vip_icon', '打手「我的」-VIP条左侧图标'), ('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_follow_kuaishou', '打手中心-关注快手'), ('fighter_mine_copy', '打手中心-复制ID'), ('fighter_mine_edit', '打手中心-编辑资料'), ('fighter_mine_qiepian', '打手中心-切片活动横幅'), ('fighter_mine_penalty_tip', '打手中心-罚单提示图标'), ('fighter_mine_kefu', '打手中心-在线客服'), ('fighter_mine_shangjia', '打手中心-商家认证'), ('fighter_complaint_record', '打手中心-投诉记录'), ('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', '通用刷新'), # 点单端「我的」服务入口(客服 / 邀请须分开可配) ('cuser_mine_kefu', '点单端我的-在线客服'), ('cuser_mine_invite', '点单端我的-邀请点单'), ('cuser_mine_invitees', '点单端我的-我推荐的人'), ('guanshi_poster_bg', '管事推广海报背景'), ('zuzhang_poster_bg', '组长推广海报背景'), ('cuser_poster_bg', '点单端推荐官海报背景'), # 打手接单池订单卡背景(未上传不下发,前端保留原样式) ('grab_card_pingtai_bg', '接单池-平台订单卡背景'), ('grab_card_youzhi_bg', '接单池-优质商家卡背景'), ('grab_card_shangjia_bg', '接单池-普通商家卡背景'), ] MINIAPP_ICON_KEYS = [k for k, _ in MINIAPP_ICON_META] MINIAPP_ICON_LABELS = dict(MINIAPP_ICON_META) # 无 DB 记录时的默认相对路径(与种子脚本一致) MINIAPP_ICON_DEFAULTS = { key: f'{ICON_FOLDER}/{key}.png' for key in MINIAPP_ICON_KEYS } # 部分历史资源扩展名不同 MINIAPP_ICON_DEFAULTS['icon_refresh'] = 'beijing/guanshiduan/icon-refresh.png' MINIAPP_ICON_DEFAULTS['merchant_refresh'] = 'beijing/guanshiduan/icon-refresh.png' MINIAPP_ICON_DEFAULTS['merchant_gold_banner'] = 'beijing/tubiao/chengxutubiao/merchant_gold_banner.png' MINIAPP_ICON_DEFAULTS['merchant_home_regular'] = 'beijing/shangjiaduan/home/regular_dispatch.png' MINIAPP_ICON_DEFAULTS['merchant_home_custom'] = 'beijing/shangjiaduan/home/custom_dispatch.png' MINIAPP_ICON_DEFAULTS['merchant_home_link'] = 'beijing/shangjiaduan/home/link_dispatch.png' MINIAPP_ICON_DEFAULTS['merchant_home_kefu_key'] = 'beijing/shangjiaduan/home/kefu_key.png' MINIAPP_ICON_DEFAULTS['merchant_home_kefu_list'] = 'beijing/shangjiaduan/home/kefu_list.png' MINIAPP_ICON_DEFAULTS['merchant_home_notice'] = 'beijing/shangjiaduan/home/notice.png' MINIAPP_ICON_DEFAULTS['merchant_home_stat_bg'] = 'beijing/shangjiaduan/stat_card_bg.png' MINIAPP_ICON_DEFAULTS['fighter_recharge_member'] = f'{ICON_FOLDER}/fighter_recharge_member.png' MINIAPP_ICON_DEFAULTS['fighter_recharge_deposit'] = f'{ICON_FOLDER}/fighter_recharge_deposit.png' MINIAPP_ICON_DEFAULTS['fighter_recharge_vip_bg'] = f'{ICON_FOLDER}/fighter_recharge_vip_bg.png' MINIAPP_ICON_DEFAULTS['fighter_mine_vip_bg'] = f'{ICON_FOLDER}/fighter_mine_vip_bg.png' MINIAPP_ICON_DEFAULTS['fighter_mine_vip_icon'] = f'{ICON_FOLDER}/fighter_mine_vip_icon.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['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_follow_kuaishou'] = 'beijing/tubiao/grzx_guanzhualong.jpg' 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_complaint_record'] = 'beijing/guanshiduan/icon-contact.png' 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['cuser_mine_kefu'] = 'beijing/tubiao/grzx_kefu.jpg' # 邀请默认用邀请图标,避免与客服同图 MINIAPP_ICON_DEFAULTS['cuser_mine_invite'] = 'beijing/guanshiduan/icon-invite.png' # 推荐的人:用下级/人员图标,勿与关注快手同图;勿用可能不存在的 chengxutubiao 占位 png MINIAPP_ICON_DEFAULTS['cuser_mine_invitees'] = 'beijing/guanshiduan/icon-sub.png' MINIAPP_ICON_DEFAULTS['guanshi_poster_bg'] = 'beijing/haibaobeijing.jpg' MINIAPP_ICON_DEFAULTS['zuzhang_poster_bg'] = 'beijing/zuzhangbeijing.jpg' # 点单端推荐官海报:独立路径,禁止复用管事/打手背景 MINIAPP_ICON_DEFAULTS['cuser_poster_bg'] = 'beijing/haibao/cuser_poster_bg.jpg' POSTER_BG_FIELDS = { 'guanshi_bg': 'guanshi_poster_bg', 'zuzhang_bg': 'zuzhang_poster_bg', 'cuser_bg': 'cuser_poster_bg', } POSTER_BG_DEFAULTS = { 'guanshi_bg': 'beijing/haibaobeijing.jpg', 'zuzhang_bg': 'beijing/zuzhangbeijing.jpg', 'cuser_bg': 'beijing/haibao/cuser_poster_bg.jpg', } POSTER_FOLDER = 'beijing/haibao' POSTER_ICON_KEYS = frozenset(POSTER_BG_FIELDS.values()) # 仅后台上传后才下发给小程序(页面大背景等) CONFIG_ONLY_DELIVERY_KEYS = frozenset([ 'fighter_recharge_vip_bg', 'fighter_mine_vip_bg', 'fighter_mine_vip_icon', 'fighter_deposit_bg', 'fighter_deposit_hero', 'grab_card_pingtai_bg', 'grab_card_youzhi_bg', 'grab_card_shangjia_bg', ]) def default_icon_path(icon_key: str) -> str: return MINIAPP_ICON_DEFAULTS.get(icon_key) or f'{ICON_FOLDER}/{icon_key}.png' def get_miniapp_icons_dict(club_id: str) -> dict: from config.models import ClubMiniappIcon 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) path = (row.image_path if row else '') or '' path = path.strip() # 已上传:必须下发(含接单池卡背景 / 充值页大图) if row and path and row.is_uploaded: result[key] = path continue if key in CONFIG_ONLY_DELIVERY_KEYS: # 兼容:有非默认路径但 is_uploaded 标志异常时仍下发 default_path = default_icon_path(key) if row and path and path != default_path: result[key] = path continue if row and path: result[key] = path else: result[key] = default_icon_path(key) return result def get_pindao_payload(club_id: str) -> dict: from config.models import ClubPindao, ClubPindaoImage row = ClubPindao.query.filter(club_id=club_id, is_active=True).first() if not row: return {'channel_no': '', 'title': '频道', 'images': []} images = [ img.image_path for img in ClubPindaoImage.query.filter(club_id=club_id).order_by('sort_order', 'id') if img.image_path ] return { 'channel_no': row.channel_no or '', 'title': row.title or '频道', 'images': images, } def get_poster_payload(club_id: str) -> dict: """各俱乐部推广海报背景相对路径(后台上传后覆盖默认)。""" from config.models import ClubMiniappIcon rows = { r.icon_key: r for r in ClubMiniappIcon.query.filter(club_id=club_id, is_active=True) } result = {} for field, icon_key in POSTER_BG_FIELDS.items(): default = POSTER_BG_DEFAULTS[field] row = rows.get(icon_key) if row and row.is_uploaded and (row.image_path or '').strip(): result[field] = row.image_path.strip() elif field == 'cuser_bg': # 点单端未上传时不回落到管事背景,避免错图 result[field] = (row.image_path.strip() if row and (row.image_path or '').strip() else '') or default else: result[field] = default return result def build_miniapp_assets_payload(request) -> dict: club_id = _display_club_id(request) return { 'club_id': club_id, 'icons': get_miniapp_icons_dict(club_id), 'pindao': get_pindao_payload(club_id), 'poster': get_poster_payload(club_id), } def seed_miniapp_icons_for_club(club_id: str, overwrite: bool = False) -> int: from config.models import ClubMiniappIcon created = 0 for key, label in MINIAPP_ICON_META: path = default_icon_path(key) row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=key).first() if row: if overwrite: row.label = label row.image_path = path row.is_active = True row.save(update_fields=['label', 'image_path', 'is_active', 'UpdateTime']) continue ClubMiniappIcon.query.create( club_id=club_id, icon_key=key, label=label, image_path=path, is_active=True, ) created += 1 return created def copy_miniapp_assets(template_club_id: str, new_club_id: str): from config.models import ClubMiniappIcon, ClubPindao, ClubPindaoImage for row in ClubMiniappIcon.query.filter(club_id=template_club_id): if ClubMiniappIcon.query.filter(club_id=new_club_id, icon_key=row.icon_key).exists(): continue ClubMiniappIcon.query.create( club_id=new_club_id, icon_key=row.icon_key, label=row.label, image_path=row.image_path, description=row.description, is_active=row.is_active, is_uploaded=row.is_uploaded, ) src_pindao = ClubPindao.query.filter(club_id=template_club_id).first() if not src_pindao or ClubPindao.query.filter(club_id=new_club_id).exists(): return new_pindao = ClubPindao.query.create( club_id=new_club_id, channel_no=src_pindao.channel_no, title=src_pindao.title, is_active=src_pindao.is_active, ) for img in ClubPindaoImage.query.filter(club_id=template_club_id).order_by('sort_order', 'id'): ClubPindaoImage.query.create( club_id=new_club_id, pindao=new_pindao, image_path=img.image_path, sort_order=img.sort_order, )