feat: 协议签署系统、海报背景资源配置组

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-06-27 02:27:31 +08:00
parent 740d64a1fd
commit f6152204d3
7 changed files with 624 additions and 1 deletions

258
houtai/xieyi_views.py Normal file
View File

@@ -0,0 +1,258 @@
"""后台 — 小程序协议管理"""
import logging
import time
import uuid
from django.db import transaction
from rest_framework import permissions
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
from peizhi.models import XcxXieyi, XcxXieyiImage, XcxXieyiQianshu
from utils.oss_utils import upload_to_oss, validate_image
from .utils import verify_kefu_permission
logger = logging.getLogger(__name__)
REQUIRED_PERMISSION = '8080a'
def _check_perm(request):
username = request.data.get('username')
kefu, perms_or_resp = verify_kefu_permission(request, username)
if kefu is None:
return None, perms_or_resp
if REQUIRED_PERMISSION not in perms_or_resp:
return None, Response({'code': 403, 'msg': '您没有权限访问协议管理(需 8080a'})
return kefu, None
def _serialize_admin(item):
return {
'id': item.id,
'title': item.title,
'shenfen': item.shenfen,
'shenfen_label': item.get_shenfen_display(),
'mode': item.mode,
'content_title': item.content_title or '',
'content_text': item.content_text or '',
'version': item.version,
'is_active': item.is_active,
'sort_order': item.sort_order,
'images': [
{'id': img.id, 'image_path': img.image_path, 'sort_order': img.sort_order}
for img in item.images.all().order_by('sort_order', 'id')
],
'created_at': item.created_at.strftime('%Y-%m-%d %H:%M:%S') if item.created_at else '',
'updated_at': item.updated_at.strftime('%Y-%m-%d %H:%M:%S') if item.updated_at else '',
}
class XieyiListAPIView(APIView):
"""POST /houtai/hthqxy — 协议列表"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _check_perm(request)
if kefu is None:
return err
shenfen = request.data.get('shenfen')
qs = XcxXieyi.objects.prefetch_related('images').all().order_by('sort_order', 'id')
if shenfen:
qs = qs.filter(shenfen=shenfen)
items = [_serialize_admin(x) for x in qs]
scenes = [{'key': c[0], 'label': c[1]} for c in XcxXieyi.SHENFEN_CHOICES]
modes = [{'key': c[0], 'label': c[1]} for c in XcxXieyi.MODE_CHOICES]
return Response({'code': 0, 'data': {'items': items, 'shenfen_choices': scenes, 'mode_choices': modes}})
class XieyiModifyAPIView(APIView):
"""POST /houtai/htxgxy — 协议增删改"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _check_perm(request)
if kefu is None:
return err
action = request.data.get('action')
if action == 'create':
return self._create(request)
if action == 'update':
return self._update(request)
if action == 'delete':
return self._delete(request)
return Response({'code': 400, 'msg': '未知 action'})
def _create(self, request):
title = (request.data.get('title') or '').strip()
shenfen = (request.data.get('shenfen') or '').strip()
mode = int(request.data.get('mode') or XcxXieyi.MODE_TEXT)
if not title or not shenfen:
return Response({'code': 400, 'msg': 'title、shenfen 必填'})
valid_shenfen = {c[0] for c in XcxXieyi.SHENFEN_CHOICES}
if shenfen not in valid_shenfen:
return Response({'code': 400, 'msg': '无效身份'})
item = XcxXieyi.objects.create(
title=title,
shenfen=shenfen,
mode=mode,
content_title=request.data.get('content_title', ''),
content_text=request.data.get('content_text', ''),
is_active=bool(request.data.get('is_active', True)),
sort_order=int(request.data.get('sort_order', 0)),
)
return Response({'code': 0, 'msg': '创建成功', 'data': _serialize_admin(item)})
def _update(self, request):
item_id = request.data.get('id')
if not item_id:
return Response({'code': 400, 'msg': '缺少 id'})
try:
item = XcxXieyi.objects.prefetch_related('images').get(id=item_id)
except XcxXieyi.DoesNotExist:
return Response({'code': 404, 'msg': '记录不存在'})
content_changed = False
if 'title' in request.data:
item.title = request.data.get('title', '')
if 'shenfen' in request.data:
shenfen = request.data.get('shenfen')
valid_shenfen = {c[0] for c in XcxXieyi.SHENFEN_CHOICES}
if shenfen not in valid_shenfen:
return Response({'code': 400, 'msg': '无效身份'})
item.shenfen = shenfen
if 'mode' in request.data:
item.mode = int(request.data.get('mode'))
content_changed = True
if 'content_title' in request.data:
item.content_title = request.data.get('content_title', '')
content_changed = True
if 'content_text' in request.data:
item.content_text = request.data.get('content_text', '')
content_changed = True
if 'is_active' in request.data:
item.is_active = bool(request.data.get('is_active'))
if 'sort_order' in request.data:
item.sort_order = int(request.data.get('sort_order', 0))
if 'bump_version' in request.data and request.data.get('bump_version'):
content_changed = True
if content_changed:
item.version = (item.version or 1) + 1
item.save()
return Response({'code': 0, 'msg': '更新成功', 'data': _serialize_admin(item)})
def _delete(self, request):
item_id = request.data.get('id')
if not item_id:
return Response({'code': 400, 'msg': '缺少 id'})
deleted, _ = XcxXieyi.objects.filter(id=item_id).delete()
if not deleted:
return Response({'code': 404, 'msg': '记录不存在'})
return Response({'code': 0, 'msg': '删除成功'})
class XieyiImageUploadAPIView(APIView):
"""POST /houtai/htxgytp — 上传协议图片"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
kefu, err = _check_perm(request)
if kefu is None:
return err
xieyi_id = request.data.get('xieyi_id') or request.data.get('id')
image_file = request.FILES.get('image')
if not xieyi_id or not image_file:
return Response({'code': 400, 'msg': 'xieyi_id 与 image 必填'})
is_valid, error_msg = validate_image(image_file)
if not is_valid:
return Response({'code': 400, 'msg': error_msg})
try:
item = XcxXieyi.objects.get(id=xieyi_id)
except XcxXieyi.DoesNotExist:
return Response({'code': 404, 'msg': '协议不存在'})
oss_path = f'a_long/xieyi/{item.id}/{int(time.time())}_{uuid.uuid4().hex[:8]}.jpg'
image_file.seek(0)
full_url = upload_to_oss(image_file, oss_path)
if not full_url:
return Response({'code': 500, 'msg': '上传失败'})
with transaction.atomic():
img = XcxXieyiImage.objects.create(
xieyi=item,
image_path=oss_path,
sort_order=int(request.data.get('sort_order', 0)),
)
item.version = (item.version or 1) + 1
item.save(update_fields=['version', 'updated_at'])
return Response({
'code': 0,
'msg': '上传成功',
'data': {'id': img.id, 'image_path': oss_path, 'version': item.version},
})
class XieyiImageDeleteAPIView(APIView):
"""POST /houtai/htscxytp — 删除协议图片"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _check_perm(request)
if kefu is None:
return err
img_id = request.data.get('image_id')
if not img_id:
return Response({'code': 400, 'msg': '缺少 image_id'})
try:
img = XcxXieyiImage.objects.select_related('xieyi').get(id=img_id)
except XcxXieyiImage.DoesNotExist:
return Response({'code': 404, 'msg': '图片不存在'})
xieyi = img.xieyi
img.delete()
xieyi.version = (xieyi.version or 1) + 1
xieyi.save(update_fields=['version', 'updated_at'])
return Response({'code': 0, 'msg': '删除成功'})
class XieyiSignRecordAPIView(APIView):
"""POST /houtai/hthqxyqs — 查询签署记录"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
kefu, err = _check_perm(request)
if kefu is None:
return err
yonghuid = (request.data.get('yonghuid') or '').strip()
xieyi_id = request.data.get('xieyi_id')
qs = XcxXieyiQianshu.objects.select_related('xieyi').order_by('-signed_at')
if yonghuid:
qs = qs.filter(yonghuid=yonghuid)
if xieyi_id:
qs = qs.filter(xieyi_id=xieyi_id)
items = []
for r in qs[:200]:
items.append({
'id': r.id,
'yonghuid': r.yonghuid,
'xieyi_id': r.xieyi_id,
'xieyi_title': r.xieyi.title,
'xieyi_version': r.xieyi_version,
'shenfen': r.shenfen,
'signature_path': r.signature_path,
'signed_at': r.signed_at.strftime('%Y-%m-%d %H:%M:%S'),
'client_ip': r.client_ip,
})
return Response({'code': 0, 'data': {'items': items}})