"""小程序展示配置:轮播图 / 公告(按俱乐部)。""" import os import random import string from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from backend.utils import verify_kefu_permission from jituan.services.club_context import resolve_effective_club_id from jituan.services.display_config import ( forbid_display_write_in_all_scope, get_gonggao_content, get_lunbo_urls, list_response_meta, list_tupianpeizhi_by_type, LUNBO_PAGE_OPTIONS, lunbo_image_type_for_page, normalize_page_key, TUPIAN_TYPE_LABELS, upsert_tupianpeizhi_single, ) from config.models import Gonggao, Lunbo, Tupianpeizhi from products.views import delete_from_oss, upload_to_oss, validate_image POPUP_PERM = '8080a' class ClubLunboListView(APIView): """GET 轮播列表 POST /jituan/houtai/lunbo-list""" permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = (request.data.get('phone') or request.data.get('username') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服账号'}) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if POPUP_PERM not in permissions: return Response({'code': 403, 'msg': '无权限管理轮播图'}) page_key = normalize_page_key(request.data.get('page_key'), image_type=1) image_type = lunbo_image_type_for_page(page_key) urls = get_lunbo_urls(request, page_key=page_key, image_type=image_type) return Response({ 'code': 0, 'data': { 'page_key': page_key, 'list': urls, 'page_options': [{'key': k, 'label': v} for k, v in LUNBO_PAGE_OPTIONS], **list_response_meta(request), }, }) class ClubLunboManageView(APIView): """轮播增删 POST /jituan/houtai/lunbo-manage""" permission_classes = [IsAuthenticated] parser_classes = [MultiPartParser, FormParser, JSONParser] def post(self, request): phone = (request.data.get('phone') or request.data.get('username') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服账号'}) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if POPUP_PERM not in permissions: return Response({'code': 403, 'msg': '无权限管理轮播图'}) deny = forbid_display_write_in_all_scope(request) if deny: return deny action = (request.data.get('action') or '').strip() club_id = resolve_effective_club_id(request, request.user) page_key = normalize_page_key(request.data.get('page_key'), image_type=1) image_type = lunbo_image_type_for_page(page_key) if action == 'delete': tupian_url = (request.data.get('tupian_url') or '').strip() if not tupian_url: return Response({'code': 400, 'msg': '图片URL不能为空'}) row = Lunbo.query.filter( club_id=club_id, ImageURL=tupian_url, ImageType=image_type, page_key=page_key, ).first() if not row: return Response({'code': 404, 'msg': '轮播图不存在'}) delete_from_oss(tupian_url) row.delete() return Response({'code': 0, 'msg': '删除成功'}) if action == 'add': file_obj = request.FILES.get('file') if not file_obj: return Response({'code': 400, 'msg': '请上传图片'}) ok, msg = validate_image(file_obj) if not ok: return Response({'code': 400, 'msg': msg}) ext = os.path.splitext(file_obj.name)[1].lower() or '.jpg' random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) oss_file_path = f"a_long/lunbo/{random_str}{ext}" if not upload_to_oss(file_obj, oss_file_path): return Response({'code': 500, 'msg': '图片上传失败'}) Lunbo.query.create( club_id=club_id, page_key=page_key, ImageURL=oss_file_path, ImageType=image_type, ) return Response({'code': 0, 'msg': '添加成功', 'data': {'tupian_url': oss_file_path}}) return Response({'code': 400, 'msg': 'action 必须为 add 或 delete'}) class ClubGonggaoView(APIView): """公告读写 POST /jituan/houtai/gonggao""" permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = (request.data.get('phone') or request.data.get('username') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服账号'}) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if POPUP_PERM not in permissions: return Response({'code': 403, 'msg': '无权限管理公告'}) notice_type = int(request.data.get('notice_type', 1) or 1) page_key = normalize_page_key(request.data.get('page_key'), image_type=1) club_id = resolve_effective_club_id(request, request.user) content = request.data.get('content') if content is None: return Response({ 'code': 0, 'data': { 'content': get_gonggao_content(request, notice_type=notice_type, page_key=page_key), 'notice_type': notice_type, 'page_key': page_key, **list_response_meta(request), }, }) deny = forbid_display_write_in_all_scope(request) if deny: return deny row, _ = Gonggao.query.get_or_create( club_id=club_id, page_key=page_key, defaults={'NoticeType': notice_type, 'Content': content}, ) row.Content = content row.NoticeType = notice_type row.save(update_fields=['Content', 'NoticeType', 'UpdateTime']) return Response({'code': 0, 'msg': '保存成功', 'data': {'page_key': page_key}}) class ClubTupianListView(APIView): """杂项图片配置列表 POST /jituan/houtai/tupian-list""" permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = (request.data.get('phone') or request.data.get('username') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服账号'}) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if POPUP_PERM not in permissions: return Response({'code': 403, 'msg': '无权限管理小程序图片'}) club_id = resolve_effective_club_id(request, request.user) data = {} for t, label in TUPIAN_TYPE_LABELS.items(): data[str(t)] = { 'label': label, 'multi': t == 3, 'list': list_tupianpeizhi_by_type(club_id, t), } return Response({ 'code': 0, 'data': { 'types': data, **list_response_meta(request), }, }) class ClubTupianManageView(APIView): """杂项图片上传/删除 POST /jituan/houtai/tupian-manage""" permission_classes = [IsAuthenticated] parser_classes = [MultiPartParser, FormParser, JSONParser] def post(self, request): phone = (request.data.get('phone') or request.data.get('username') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服账号'}) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if POPUP_PERM not in permissions: return Response({'code': 403, 'msg': '无权限管理小程序图片'}) deny = forbid_display_write_in_all_scope(request) if deny: return deny action = (request.data.get('action') or '').strip() image_type = int(request.data.get('image_type') or 0) if image_type not in TUPIAN_TYPE_LABELS: return Response({'code': 400, 'msg': 'image_type 无效'}) club_id = resolve_effective_club_id(request, request.user) if action == 'delete': row_id = request.data.get('id') image_url = (request.data.get('image_url') or '').strip() qs = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type) if row_id: row = qs.filter(id=row_id).first() elif image_url: row = qs.filter(ImageURL=image_url).first() else: return Response({'code': 400, 'msg': '缺少 id 或 image_url'}) if not row: return Response({'code': 404, 'msg': '图片不存在'}) if row.ImageURL: delete_from_oss(row.ImageURL) row.delete() return Response({'code': 0, 'msg': '删除成功'}) if action == 'add': file_obj = request.FILES.get('file') if not file_obj: return Response({'code': 400, 'msg': '请上传图片'}) ok, msg = validate_image(file_obj) if not ok: return Response({'code': 400, 'msg': msg}) ext = os.path.splitext(file_obj.name)[1].lower() or '.jpg' random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) folder = 'guanzhu' if image_type == 3 else 'misc' oss_file_path = f"beijing/{folder}/{club_id}_{random_str}{ext}" if not upload_to_oss(file_obj, oss_file_path): return Response({'code': 500, 'msg': '图片上传失败'}) if image_type in (1, 2): upsert_tupianpeizhi_single(club_id, image_type, oss_file_path) else: Tupianpeizhi.query.create( club_id=club_id, ImageType=image_type, ImageURL=oss_file_path, AccessCount=0, ) return Response({'code': 0, 'msg': '上传成功', 'data': {'image_url': oss_file_path}}) return Response({'code': 400, 'msg': 'action 必须为 add 或 delete'})