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

View File

@@ -18,6 +18,10 @@ from .view import GetClubConfigView, GetCrossOrderListView, PartnerGetOrderDataV
CaiwuView, CwhybkhqView, HybkjtsjView, SzxxView, CwddhqlxView, CwhqjtddsjView, CwqtczhqView, KhpzhqView, ChzsgcView, \
KhgglView, ShgxgsjView, ZxkfghdsView, KptxwztjbView
from .huashu_views import HuashuListAPIView, HuashuModifyAPIView
from .xieyi_views import (
XieyiListAPIView, XieyiModifyAPIView, XieyiImageUploadAPIView,
XieyiImageDeleteAPIView, XieyiSignRecordAPIView,
)
urlpatterns = [
@@ -89,6 +93,11 @@ urlpatterns = [
path('htxgtcxx', PopupNoticeModifyAPIView.as_view(), name='后台更新创建弹窗配置'),
path('hthqhs', HuashuListAPIView.as_view(), name='后台获取话术配置'),
path('htxghs', HuashuModifyAPIView.as_view(), name='后台更新话术配置'),
path('hthqxy', XieyiListAPIView.as_view(), name='后台获取协议列表'),
path('htxgxy', XieyiModifyAPIView.as_view(), name='后台协议增删改'),
path('htxgytp', XieyiImageUploadAPIView.as_view(), name='后台上传协议图片'),
path('htscxytp', XieyiImageDeleteAPIView.as_view(), name='后台删除协议图片'),
path('hthqxyqs', XieyiSignRecordAPIView.as_view(), name='后台查询协议签署记录'),
path('kffkdssq', FineApplyView.as_view(), name='跨平台处罚罚款申请接口'),
path('kffkdssq_notify', PartnerFineNotifyView.as_view(), name='跨屏要对方平台处罚通知接口'),

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

View File

@@ -0,0 +1,93 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('peizhi', '0016_xcx_sys_peizhi_page_ziyuan'),
]
operations = [
migrations.CreateModel(
name='XcxXieyi',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, verbose_name='后台标题')),
('shenfen', models.CharField(
choices=[
('dashou', '打手'),
('guanshi', '管事'),
('zuzhang', '组长'),
('shangjia', '商家'),
('normal', '点单老板'),
],
db_index=True,
max_length=20,
verbose_name='适用身份',
)),
('mode', models.PositiveSmallIntegerField(
choices=[(1, '纯图片'), (2, '文字')],
default=2,
verbose_name='展示模式',
)),
('content_title', models.CharField(blank=True, default='', max_length=200, verbose_name='前端展示标题')),
('content_text', models.TextField(blank=True, default='', verbose_name='文字内容')),
('version', models.PositiveIntegerField(default=1, verbose_name='版本号')),
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
('sort_order', models.IntegerField(default=0, verbose_name='排序')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'verbose_name': '小程序协议',
'verbose_name_plural': '小程序协议',
'db_table': 'xcx_xieyi',
'ordering': ['sort_order', 'id'],
},
),
migrations.CreateModel(
name='XcxXieyiImage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image_path', models.CharField(max_length=500, verbose_name='OSS相对路径')),
('sort_order', models.IntegerField(default=0, verbose_name='排序')),
('xieyi', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='images',
to='peizhi.xcxxieyi',
verbose_name='协议',
)),
],
options={
'verbose_name': '协议图片',
'verbose_name_plural': '协议图片',
'db_table': 'xcx_xieyi_image',
'ordering': ['sort_order', 'id'],
},
),
migrations.CreateModel(
name='XcxXieyiQianshu',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('yonghuid', models.CharField(db_index=True, max_length=7, verbose_name='用户ID')),
('xieyi_version', models.PositiveIntegerField(verbose_name='签署时协议版本')),
('shenfen', models.CharField(max_length=20, verbose_name='签署时身份')),
('signature_path', models.CharField(max_length=500, verbose_name='签名图OSS路径')),
('signed_at', models.DateTimeField(auto_now_add=True, verbose_name='签署时间')),
('client_ip', models.CharField(blank=True, default='', max_length=64, verbose_name='客户端IP')),
('xieyi', models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='sign_records',
to='peizhi.xcxxieyi',
verbose_name='协议',
)),
],
options={
'verbose_name': '协议签署记录',
'verbose_name_plural': '协议签署记录',
'db_table': 'xcx_xieyi_qianshu',
'indexes': [models.Index(fields=['yonghuid', 'xieyi'], name='xcx_xieyi_q_yid_xy_idx')],
},
),
]

View File

@@ -721,4 +721,76 @@ class XcxPageZiyuan(models.Model):
indexes = [models.Index(fields=['ziyuan_zu'])]
def __str__(self):
return f'{self.ziyuan_zu}.{self.ziyuan_key}'
return f'{self.ziyuan_zu}.{self.ziyuan_key}'
class XcxXieyi(models.Model):
"""小程序用户协议配置"""
MODE_IMAGE = 1
MODE_TEXT = 2
MODE_CHOICES = (
(MODE_IMAGE, '纯图片'),
(MODE_TEXT, '文字'),
)
SHENFEN_CHOICES = (
('dashou', '打手'),
('guanshi', '管事'),
('zuzhang', '组长'),
('shangjia', '商家'),
('normal', '点单老板'),
)
title = models.CharField(max_length=200, verbose_name='后台标题')
shenfen = models.CharField(max_length=20, choices=SHENFEN_CHOICES, db_index=True, verbose_name='适用身份')
mode = models.PositiveSmallIntegerField(choices=MODE_CHOICES, default=MODE_TEXT, verbose_name='展示模式')
content_title = models.CharField(max_length=200, blank=True, default='', verbose_name='前端展示标题')
content_text = models.TextField(blank=True, default='', verbose_name='文字内容')
version = models.PositiveIntegerField(default=1, verbose_name='版本号')
is_active = models.BooleanField(default=True, verbose_name='是否启用')
sort_order = models.IntegerField(default=0, verbose_name='排序')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'xcx_xieyi'
verbose_name = '小程序协议'
verbose_name_plural = verbose_name
ordering = ['sort_order', 'id']
def __str__(self):
return f'{self.title}({self.get_shenfen_display()})'
class XcxXieyiImage(models.Model):
"""协议图片(纯图片模式,支持多张)"""
xieyi = models.ForeignKey(XcxXieyi, on_delete=models.CASCADE, related_name='images', verbose_name='协议')
image_path = models.CharField(max_length=500, verbose_name='OSS相对路径')
sort_order = models.IntegerField(default=0, verbose_name='排序')
class Meta:
db_table = 'xcx_xieyi_image'
verbose_name = '协议图片'
verbose_name_plural = verbose_name
ordering = ['sort_order', 'id']
class XcxXieyiQianshu(models.Model):
"""用户协议签署记录"""
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
xieyi = models.ForeignKey(XcxXieyi, on_delete=models.CASCADE, related_name='sign_records', verbose_name='协议')
xieyi_version = models.PositiveIntegerField(verbose_name='签署时协议版本')
shenfen = models.CharField(max_length=20, verbose_name='签署时身份')
signature_path = models.CharField(max_length=500, verbose_name='签名图OSS路径')
signed_at = models.DateTimeField(auto_now_add=True, verbose_name='签署时间')
client_ip = models.CharField(max_length=64, blank=True, default='', verbose_name='客户端IP')
class Meta:
db_table = 'xcx_xieyi_qianshu'
verbose_name = '协议签署记录'
verbose_name_plural = verbose_name
indexes = [
models.Index(fields=['yonghuid', 'xieyi']),
]

View File

@@ -11,6 +11,7 @@ from .xcx_config_views import (
XcxSysPeizhiQueryView, XcxSysPeizhiUpdateView,
XcxPageZiyuanQueryView, XcxPageZiyuanUpdateView, XcxPageZiyuanUploadView,
)
from .xieyi_views import XieyiListView, XieyiDetailView, XieyiSignStatusView, XieyiSignView
urlpatterns = [
path('shangpingonggao/', ShangpinGonggaoView.as_view(), name='商品公告轮播获取'),
@@ -52,4 +53,8 @@ urlpatterns = [
path('xcxyzycx', XcxPageZiyuanQueryView.as_view(), name='管理员查询页面资源'),
path('xcxyzgx', XcxPageZiyuanUpdateView.as_view(), name='管理员更新页面资源'),
path('xcxyzsc', XcxPageZiyuanUploadView.as_view(), name='管理员上传页面资源'),
path('xieyihq', XieyiListView.as_view(), name='小程序获取协议列表'),
path('xieyixq', XieyiDetailView.as_view(), name='小程序获取协议详情'),
path('xieyizt', XieyiSignStatusView.as_view(), name='小程序查询协议签署状态'),
path('xieyiqm', XieyiSignView.as_view(), name='小程序签署协议'),
]

180
peizhi/xieyi_views.py Normal file
View File

@@ -0,0 +1,180 @@
"""小程序协议 — 查询与签署"""
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
logger = logging.getLogger(__name__)
VALID_SHENFEN = frozenset({'dashou', 'guanshi', 'zuzhang', 'shangjia', 'normal'})
def _client_ip(request):
xff = request.META.get('HTTP_X_FORWARDED_FOR')
if xff:
ip = xff.split(',')[0].strip()
if ip:
return ip
return request.META.get('REMOTE_ADDR', '') or ''
def _serialize_xieyi(item, yonghuid=None, include_images=False):
signed = False
if yonghuid:
signed = XcxXieyiQianshu.objects.filter(
yonghuid=yonghuid, xieyi_id=item.id, xieyi_version=item.version,
).exists()
data = {
'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,
'signed': signed,
}
if include_images:
data['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')
]
return data
class XieyiListView(APIView):
"""POST /peizhi/xieyihq — 按身份获取启用协议列表"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
shenfen = (request.data.get('shenfen') or '').strip()
if shenfen not in VALID_SHENFEN:
return Response({'code': 400, 'msg': '无效的身份参数', 'data': {'list': []}})
yonghuid = request.user.yonghuid
qs = XcxXieyi.objects.filter(shenfen=shenfen, is_active=True).order_by('sort_order', 'id')
items = [_serialize_xieyi(x, yonghuid) for x in qs]
return Response({'code': 0, 'msg': 'success', 'data': {'list': items}})
class XieyiDetailView(APIView):
"""POST /peizhi/xieyixq — 协议详情"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
xieyi_id = request.data.get('id') or request.data.get('xieyi_id')
if not xieyi_id:
return Response({'code': 400, 'msg': '缺少协议 id', 'data': None})
try:
item = XcxXieyi.objects.prefetch_related('images').get(id=xieyi_id, is_active=True)
except XcxXieyi.DoesNotExist:
return Response({'code': 404, 'msg': '协议不存在或已下线', 'data': None})
yonghuid = request.user.yonghuid
return Response({
'code': 0,
'msg': 'success',
'data': _serialize_xieyi(item, yonghuid, include_images=True),
})
class XieyiSignStatusView(APIView):
"""POST /peizhi/xieyizt — 是否已签署当前版本"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
xieyi_id = request.data.get('id') or request.data.get('xieyi_id')
if not xieyi_id:
return Response({'code': 400, 'msg': '缺少协议 id', 'data': None})
try:
item = XcxXieyi.objects.get(id=xieyi_id, is_active=True)
except XcxXieyi.DoesNotExist:
return Response({'code': 404, 'msg': '协议不存在', 'data': None})
yonghuid = request.user.yonghuid
record = XcxXieyiQianshu.objects.filter(
yonghuid=yonghuid, xieyi_id=item.id, xieyi_version=item.version,
).order_by('-signed_at').first()
return Response({
'code': 0,
'msg': 'success',
'data': {
'signed': record is not None,
'version': item.version,
'signed_at': record.signed_at.strftime('%Y-%m-%d %H:%M:%S') if record else '',
'signature_path': record.signature_path if record else '',
},
})
class XieyiSignView(APIView):
"""POST /peizhi/xieyiqm — 上传手写签名并完成签署"""
permission_classes = [permissions.IsAuthenticated]
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
xieyi_id = request.data.get('xieyi_id') or request.data.get('id')
shenfen = (request.data.get('shenfen') or '').strip()
image_file = request.FILES.get('image') or request.FILES.get('file')
if not xieyi_id:
return Response({'code': 1, 'msg': '缺少 xieyi_id', 'data': None})
if not image_file:
return Response({'code': 2, 'msg': '请上传签名图片', 'data': None})
if shenfen and shenfen not in VALID_SHENFEN:
return Response({'code': 3, 'msg': '无效身份', 'data': None})
is_valid, error_msg = validate_image(image_file)
if not is_valid:
return Response({'code': 4, 'msg': error_msg, 'data': None})
try:
item = XcxXieyi.objects.get(id=xieyi_id, is_active=True)
except XcxXieyi.DoesNotExist:
return Response({'code': 404, 'msg': '协议不存在或已下线', 'data': None})
yonghuid = request.user.yonghuid
if XcxXieyiQianshu.objects.filter(
yonghuid=yonghuid, xieyi_id=item.id, xieyi_version=item.version,
).exists():
return Response({'code': 5, 'msg': '您已签署过当前版本协议', 'data': None})
oss_path = f'qianming/xieyi/{yonghuid}/{item.id}_{int(time.time())}_{uuid.uuid4().hex[:8]}.png'
image_file.seek(0)
full_url = upload_to_oss(image_file, oss_path)
if not full_url:
return Response({'code': 6, 'msg': '签名上传失败', 'data': None})
sign_shenfen = shenfen or item.shenfen
with transaction.atomic():
record = XcxXieyiQianshu.objects.create(
yonghuid=yonghuid,
xieyi=item,
xieyi_version=item.version,
shenfen=sign_shenfen,
signature_path=oss_path,
client_ip=_client_ip(request),
)
return Response({
'code': 0,
'msg': '签署成功',
'data': {
'id': record.id,
'signature_path': oss_path,
'signed_at': record.signed_at.strftime('%Y-%m-%d %H:%M:%S'),
},
})

View File

@@ -42,6 +42,8 @@ DEFAULT_ZIYUAN = {
'guanshiduan.iconWithdraw': 'beijing/guanshiduan/icon-withdraw.png',
'guanshiduan.iconContact': 'beijing/guanshiduan/icon-contact.png',
'guanshiduan.iconArrow': 'beijing/guanshiduan/icon-arrow.png',
'haibao.guanshiBg': 'beijing/haibaobeijing.jpg',
'haibao.zuzhangBg': 'beijing/zuzhangbeijing.jpg',
}
# 后台配置页展示用中文说明(键名 pageBg 等仅供程序识别)
@@ -82,6 +84,8 @@ ZIYUAN_MIAOSHU = {
'guanshiduan.iconWithdraw': '管事端 · 提现图标',
'guanshiduan.iconContact': '管事端 · 联系客服图标',
'guanshiduan.iconArrow': '管事端 · 箭头/跳转图标',
'haibao.guanshiBg': '管事推广海报 · 背景图',
'haibao.zuzhangBg': '组长推广海报 · 背景图',
}
@@ -95,6 +99,7 @@ def clear_page_ziyuan_cache():
f'{_CACHE_PREFIX}:paihangbang',
f'{_CACHE_PREFIX}:shangjiaduan',
f'{_CACHE_PREFIX}:guanshiduan',
f'{_CACHE_PREFIX}:haibao',
])
@@ -152,4 +157,5 @@ def build_page_assets_for_api():
'shangjiaduan': get_ziyuan_group('shangjiaduan'),
'paihangbang': get_ziyuan_group('paihangbang'),
'guanshiduan': get_ziyuan_group('guanshiduan'),
'haibao': get_ziyuan_group('haibao'),
}