feat: 小程序图标与频道配置 API(按俱乐部)
This commit is contained in:
67
config/migrations/0010_club_miniapp_icon_pindao.py
Normal file
67
config/migrations/0010_club_miniapp_icon_pindao.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config', '0009_gonggao_copy_pages'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ClubMiniappIcon',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')),
|
||||
('icon_key', models.CharField(db_index=True, max_length=64, verbose_name='图标键')),
|
||||
('label', models.CharField(blank=True, default='', max_length=100, verbose_name='展示名称')),
|
||||
('image_path', models.CharField(blank=True, default='', max_length=500, verbose_name='图片相对路径')),
|
||||
('description', models.CharField(blank=True, default='', max_length=300, verbose_name='说明')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '小程序图标',
|
||||
'verbose_name_plural': '小程序图标',
|
||||
'db_table': 'club_miniapp_icon',
|
||||
'ordering': ['icon_key'],
|
||||
'unique_together': {('club_id', 'icon_key')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubPindao',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(default='xq', max_length=16, unique=True, verbose_name='俱乐部ID')),
|
||||
('channel_no', models.CharField(blank=True, default='', max_length=100, verbose_name='频道号')),
|
||||
('title', models.CharField(blank=True, default='频道', max_length=100, verbose_name='标题')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='是否启用')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '俱乐部频道',
|
||||
'verbose_name_plural': '俱乐部频道',
|
||||
'db_table': 'club_pindao',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClubPindaoImage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('club_id', models.CharField(db_index=True, default='xq', max_length=16, verbose_name='俱乐部ID')),
|
||||
('image_path', models.CharField(max_length=500, verbose_name='图片相对路径')),
|
||||
('sort_order', models.PositiveIntegerField(default=0, verbose_name='排序')),
|
||||
('CreateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('pindao', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='config.clubpindao')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '频道图片',
|
||||
'verbose_name_plural': '频道图片',
|
||||
'db_table': 'club_pindao_image',
|
||||
'ordering': ['sort_order', 'id'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -459,3 +459,63 @@ class PopupImage(QModel):
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.popup_config.popup_id} - 图片{self.sort_order}'
|
||||
|
||||
|
||||
class ClubMiniappIcon(QModel):
|
||||
"""小程序功能图标(按俱乐部,固定 icon_key + 存储桶相对路径)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
icon_key = models.CharField(max_length=64, db_index=True, verbose_name='图标键')
|
||||
label = models.CharField(max_length=100, blank=True, default='', verbose_name='展示名称')
|
||||
image_path = models.CharField(max_length=500, blank=True, default='', verbose_name='图片相对路径')
|
||||
description = models.CharField(max_length=300, blank=True, default='', verbose_name='说明')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_miniapp_icon'
|
||||
verbose_name = '小程序图标'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = [['club_id', 'icon_key']]
|
||||
ordering = ['icon_key']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id}:{self.icon_key}'
|
||||
|
||||
|
||||
class ClubPindao(QModel):
|
||||
"""俱乐部频道配置(每俱乐部一条)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', unique=True, verbose_name='俱乐部ID')
|
||||
channel_no = models.CharField(max_length=100, blank=True, default='', verbose_name='频道号')
|
||||
title = models.CharField(max_length=100, blank=True, default='频道', verbose_name='标题')
|
||||
is_active = models.BooleanField(default=True, verbose_name='是否启用')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_pindao'
|
||||
verbose_name = '俱乐部频道'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id} 频道'
|
||||
|
||||
|
||||
class ClubPindaoImage(QModel):
|
||||
"""频道展示图片(多张)。"""
|
||||
club_id = models.CharField(max_length=16, default='xq', db_index=True, verbose_name='俱乐部ID')
|
||||
pindao = models.ForeignKey(
|
||||
ClubPindao, on_delete=models.CASCADE, related_name='images', null=True, blank=True,
|
||||
)
|
||||
image_path = models.CharField(max_length=500, verbose_name='图片相对路径')
|
||||
sort_order = models.PositiveIntegerField(default=0, verbose_name='排序')
|
||||
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||
|
||||
class Meta:
|
||||
db_table = 'club_pindao_image'
|
||||
verbose_name = '频道图片'
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ['sort_order', 'id']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.club_id} 频道图{self.sort_order}'
|
||||
|
||||
@@ -67,6 +67,8 @@ class GetDynamicConfigView(APIView):
|
||||
try:
|
||||
from jituan.services.display_config import get_tupianpeizhi_url
|
||||
|
||||
from jituan.services.miniapp_assets import build_miniapp_assets_payload
|
||||
|
||||
cos_oss_url = getattr(settings, 'COS_DOMAIN', '')
|
||||
if cos_oss_url and not cos_oss_url.endswith('/'):
|
||||
cos_oss_url += '/'
|
||||
@@ -102,7 +104,8 @@ class GetDynamicConfigView(APIView):
|
||||
"kefu": {
|
||||
"link": kefu_link,
|
||||
"enterpriseId": kefu_enterprise_id
|
||||
}
|
||||
},
|
||||
"miniappAssets": build_miniapp_assets_payload(request),
|
||||
}
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
@@ -182,3 +182,6 @@ def copy_display_config(template_club_id, new_club_id):
|
||||
text=img.text,
|
||||
sort_order=img.sort_order,
|
||||
)
|
||||
|
||||
from jituan.services.miniapp_assets import copy_miniapp_assets
|
||||
copy_miniapp_assets(template_club_id, new_club_id)
|
||||
|
||||
139
jituan/services/miniapp_assets.py
Normal file
139
jituan/services/miniapp_assets.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""小程序频道与可配置图标(按俱乐部)。"""
|
||||
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_regular_dispatch', '常规派单'),
|
||||
('merchant_custom_dispatch', '自定义派单'),
|
||||
('merchant_pending_settle', '待清算'),
|
||||
('merchant_kefu_key', '客服密钥'),
|
||||
('merchant_kefu_list', '客服列表'),
|
||||
('merchant_recharge_deposit', '充值保证金'),
|
||||
('merchant_recharge_member', '充值会员'),
|
||||
('merchant_link_dispatch', '链接派单'),
|
||||
('merchant_punish', '处罚记录'),
|
||||
('merchant_rank', '商家排行'),
|
||||
('merchant_refresh', '商家刷新'),
|
||||
('fighter_recharge_member', '打手充值会员'),
|
||||
('fighter_recharge_deposit', '打手充值保证金'),
|
||||
('mine_pindao', '频道入口'),
|
||||
('icon_refresh', '通用刷新'),
|
||||
]
|
||||
|
||||
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['fighter_recharge_member'] = 'beijing/dashouduan/dashoutubiaobeijing/icon-recharge.jpg'
|
||||
MINIAPP_ICON_DEFAULTS['fighter_recharge_deposit'] = 'beijing/dashouduan/vip-card-bg.jpg'
|
||||
MINIAPP_ICON_DEFAULTS['mine_pindao'] = 'beijing/tubiao/grzx_guanzhualong.jpg'
|
||||
|
||||
|
||||
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 = ClubMiniappIcon.query.filter(club_id=club_id, is_active=True)
|
||||
db_map = {r.icon_key: (r.image_path or '').strip() for r in rows if r.icon_key}
|
||||
result = {}
|
||||
for key in MINIAPP_ICON_KEYS:
|
||||
path = db_map.get(key) or default_icon_path(key)
|
||||
if path:
|
||||
result[key] = path
|
||||
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 build_miniapp_assets_payload(request) -> dict:
|
||||
club_id = _display_club_id(request)
|
||||
return {
|
||||
'icons': get_miniapp_icons_dict(club_id),
|
||||
'pindao': get_pindao_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,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
@@ -26,6 +26,11 @@ from jituan.views_display import (
|
||||
ClubGonggaoView, ClubLunboListView, ClubLunboManageView,
|
||||
ClubTupianListView, ClubTupianManageView,
|
||||
)
|
||||
from jituan.views_miniapp_assets import (
|
||||
ClubMiniappIconListView,
|
||||
ClubMiniappIconManageView,
|
||||
ClubPindaoManageView,
|
||||
)
|
||||
from jituan.views import (
|
||||
ClubAdminAssignmentListView,
|
||||
ClubAuditLogListView,
|
||||
@@ -89,6 +94,9 @@ urlpatterns = [
|
||||
path('houtai/identity-tag/bind', IdentityTagBindView.as_view(), name='jituan_identity_tag_bind'),
|
||||
path('houtai/tupian-list', ClubTupianListView.as_view(), name='jituan_tupian_list'),
|
||||
path('houtai/tupian-manage', ClubTupianManageView.as_view(), name='jituan_tupian_manage'),
|
||||
path('houtai/miniapp-icon-list', ClubMiniappIconListView.as_view(), name='jituan_miniapp_icon_list'),
|
||||
path('houtai/miniapp-icon-manage', ClubMiniappIconManageView.as_view(), name='jituan_miniapp_icon_manage'),
|
||||
path('houtai/pindao-manage', ClubPindaoManageView.as_view(), name='jituan_pindao_manage'),
|
||||
path('houtai/hthqtcxx', PopupNoticeListAPIView.as_view(), name='jituan_popup_list'),
|
||||
path('houtai/htxgtcxx', PopupNoticeModifyAPIView.as_view(), name='jituan_popup_modify'),
|
||||
path('houtai/htglyhqcfsltj', FaKuanTongJiView.as_view(), name='jituan_penalty_stats'),
|
||||
|
||||
231
jituan/views_miniapp_assets.py
Normal file
231
jituan/views_miniapp_assets.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""小程序图标与频道 — 后台管理 API。"""
|
||||
import os
|
||||
|
||||
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 config.models import ClubMiniappIcon, ClubPindao, ClubPindaoImage
|
||||
from jituan.services.club_context import resolve_effective_club_id
|
||||
from jituan.services.display_config import forbid_display_write_in_all_scope, list_response_meta
|
||||
from jituan.services.miniapp_assets import (
|
||||
ICON_FOLDER,
|
||||
MINIAPP_ICON_LABELS,
|
||||
MINIAPP_ICON_META,
|
||||
default_icon_path,
|
||||
get_miniapp_icons_dict,
|
||||
get_pindao_payload,
|
||||
)
|
||||
from products.views import delete_from_oss, upload_to_oss, validate_image
|
||||
|
||||
POPUP_PERM = '8080a'
|
||||
PINDAO_FOLDER = 'beijing/pindao'
|
||||
|
||||
|
||||
class ClubMiniappIconListView(APIView):
|
||||
"""POST /jituan/houtai/miniapp-icon-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)
|
||||
icons_db = {
|
||||
r.icon_key: r for r in ClubMiniappIcon.query.filter(club_id=club_id)
|
||||
}
|
||||
items = []
|
||||
for key, label in MINIAPP_ICON_META:
|
||||
row = icons_db.get(key)
|
||||
items.append({
|
||||
'icon_key': key,
|
||||
'label': label,
|
||||
'image_path': (row.image_path if row else '') or default_icon_path(key),
|
||||
'description': (row.description if row else '') or '',
|
||||
'is_active': row.is_active if row else True,
|
||||
'id': row.id if row else None,
|
||||
})
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'icons': items,
|
||||
'folder': ICON_FOLDER,
|
||||
**list_response_meta(request),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class ClubMiniappIconManageView(APIView):
|
||||
"""POST /jituan/houtai/miniapp-icon-manage action=upload|save_meta"""
|
||||
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 'upload').strip()
|
||||
club_id = resolve_effective_club_id(request, request.user)
|
||||
icon_key = (request.data.get('icon_key') or '').strip()
|
||||
if icon_key not in MINIAPP_ICON_LABELS:
|
||||
return Response({'code': 400, 'msg': 'icon_key 无效'})
|
||||
|
||||
if action == 'save_meta':
|
||||
description = (request.data.get('description') or '').strip()
|
||||
row, _ = ClubMiniappIcon.query.get_or_create(
|
||||
club_id=club_id,
|
||||
icon_key=icon_key,
|
||||
defaults={
|
||||
'label': MINIAPP_ICON_LABELS[icon_key],
|
||||
'image_path': default_icon_path(icon_key),
|
||||
},
|
||||
)
|
||||
row.description = description
|
||||
row.label = MINIAPP_ICON_LABELS[icon_key]
|
||||
row.save(update_fields=['description', 'label', 'UpdateTime'])
|
||||
return Response({'code': 0, 'msg': '保存成功'})
|
||||
|
||||
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 '.png'
|
||||
if ext not in ('.png', '.jpg', '.jpeg', '.webp'):
|
||||
ext = '.png'
|
||||
oss_path = f'{ICON_FOLDER}/{icon_key}{ext}'
|
||||
|
||||
row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=icon_key).first()
|
||||
if row and row.image_path and row.image_path != oss_path:
|
||||
delete_from_oss(row.image_path)
|
||||
|
||||
if not upload_to_oss(file_obj, oss_path):
|
||||
return Response({'code': 500, 'msg': '图片上传失败'})
|
||||
|
||||
if row:
|
||||
row.image_path = oss_path
|
||||
row.label = MINIAPP_ICON_LABELS[icon_key]
|
||||
row.is_active = True
|
||||
row.save(update_fields=['image_path', 'label', 'is_active', 'UpdateTime'])
|
||||
else:
|
||||
ClubMiniappIcon.query.create(
|
||||
club_id=club_id,
|
||||
icon_key=icon_key,
|
||||
label=MINIAPP_ICON_LABELS[icon_key],
|
||||
image_path=oss_path,
|
||||
is_active=True,
|
||||
)
|
||||
return Response({'code': 0, 'msg': '上传成功', 'data': {'image_path': oss_path}})
|
||||
|
||||
|
||||
class ClubPindaoManageView(APIView):
|
||||
"""POST /jituan/houtai/pindao-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': '无权限管理频道'})
|
||||
|
||||
action = (request.data.get('action') or 'get').strip()
|
||||
club_id = resolve_effective_club_id(request, request.user)
|
||||
|
||||
if action == 'get':
|
||||
pindao = get_pindao_payload(club_id)
|
||||
images = [
|
||||
{
|
||||
'id': img.id,
|
||||
'image_path': img.image_path,
|
||||
'sort_order': img.sort_order,
|
||||
}
|
||||
for img in ClubPindaoImage.query.filter(club_id=club_id).order_by('sort_order', 'id')
|
||||
]
|
||||
return Response({
|
||||
'code': 0,
|
||||
'data': {
|
||||
'pindao': pindao,
|
||||
'images': images,
|
||||
'folder': PINDAO_FOLDER,
|
||||
**list_response_meta(request),
|
||||
},
|
||||
})
|
||||
|
||||
deny = forbid_display_write_in_all_scope(request)
|
||||
if deny:
|
||||
return deny
|
||||
|
||||
if action == 'save':
|
||||
channel_no = (request.data.get('channel_no') or '').strip()
|
||||
title = (request.data.get('title') or '频道').strip() or '频道'
|
||||
row, _ = ClubPindao.query.get_or_create(club_id=club_id, defaults={'title': title})
|
||||
row.channel_no = channel_no
|
||||
row.title = title
|
||||
row.is_active = True
|
||||
row.save(update_fields=['channel_no', 'title', 'is_active', 'UpdateTime'])
|
||||
return Response({'code': 0, 'msg': '保存成功'})
|
||||
|
||||
if action == 'add_image':
|
||||
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'
|
||||
import random
|
||||
import string
|
||||
rnd = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
|
||||
oss_path = f'{PINDAO_FOLDER}/{club_id}_{rnd}{ext}'
|
||||
if not upload_to_oss(file_obj, oss_path):
|
||||
return Response({'code': 500, 'msg': '图片上传失败'})
|
||||
pindao_row, _ = ClubPindao.query.get_or_create(club_id=club_id, defaults={'title': '频道'})
|
||||
max_sort = ClubPindaoImage.query.filter(club_id=club_id).order_by('-sort_order').first()
|
||||
sort_order = (max_sort.sort_order + 1) if max_sort else 0
|
||||
img = ClubPindaoImage.query.create(
|
||||
club_id=club_id,
|
||||
pindao=pindao_row,
|
||||
image_path=oss_path,
|
||||
sort_order=sort_order,
|
||||
)
|
||||
return Response({'code': 0, 'msg': '上传成功', 'data': {'id': img.id, 'image_path': oss_path}})
|
||||
|
||||
if action == 'delete_image':
|
||||
img_id = request.data.get('id')
|
||||
if not img_id:
|
||||
return Response({'code': 400, 'msg': '缺少图片 id'})
|
||||
img = ClubPindaoImage.query.filter(club_id=club_id, id=img_id).first()
|
||||
if not img:
|
||||
return Response({'code': 404, 'msg': '图片不存在'})
|
||||
if img.image_path:
|
||||
delete_from_oss(img.image_path)
|
||||
img.delete()
|
||||
return Response({'code': 0, 'msg': '删除成功'})
|
||||
|
||||
return Response({'code': 400, 'msg': 'action 无效'})
|
||||
28
scripts/seed_miniapp_icons.py
Normal file
28
scripts/seed_miniapp_icons.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python
|
||||
"""预置各俱乐部小程序图标配置(仅写 DB 路径,不上传文件)。"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, BASE_DIR)
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
|
||||
django.setup()
|
||||
|
||||
from jituan.models import Club
|
||||
from jituan.services.miniapp_assets import seed_miniapp_icons_for_club
|
||||
|
||||
|
||||
def main():
|
||||
clubs = Club.query.filter(status=1).values_list('club_id', flat=True)
|
||||
club_ids = list(clubs) or ['xq', 'xzj']
|
||||
total = 0
|
||||
for cid in club_ids:
|
||||
n = seed_miniapp_icons_for_club(cid, overwrite=False)
|
||||
total += n
|
||||
print(f'俱乐部 {cid}: 新增 {n} 条图标配置')
|
||||
print(f'完成,共新增 {total} 条')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user