Your commit message

This commit is contained in:
Jane Doe 2024-06-05 05:10:50 +08:00
parent b182234fe6
commit 06dfa7cd9d
200 changed files with 131815 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

15
.idea/deployment.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" autoUpload="Always" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="bt">
<serverdata>
<mappings>
<mapping deploy="/www/wwwroot/unapp_admin" local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
<option name="myAutoUpload" value="ALWAYS" />
</component>
</project>

9
.idea/encodings.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/AI日志.log" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MyApi/AiMssage.py" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MyApi/urls.py" charset="UTF-8" />
<file url="PROJECT" charset="GBK" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (website)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (website)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/website.iml" filepath="$PROJECT_DIR$/.idea/website.iml" />
</modules>
</component>
</project>

8
.idea/sshConfigs.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SshConfigs">
<configs>
<sshConfig authType="PASSWORD" host="81.70.187.27" id="1116324b-4de4-402e-88b4-7d91bcd3eeb2" port="22" nameFormat="DESCRIPTIVE" username="root" useOpenSSHConfig="true" />
</configs>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

14
.idea/webServers.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="a6d64284-97a3-4cf2-989c-9e8c30a5aaa1" name="bt" url="http://81.70.187.27/">
<fileTransfer accessType="SFTP" host="81.70.187.27" port="22" sshConfigId="1116324b-4de4-402e-88b4-7d91bcd3eeb2" sshConfig="root@81.70.187.27:22 password">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" isUseSudo="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

30
.idea/website.iml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="website/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/../website\templates" />
</list>
</option>
</component>
</module>

26
MyWeb/WXBizDataCrypt.py Normal file
View File

@ -0,0 +1,26 @@
from base64 import b64decode
from Crypto.Cipher import AES
import json
class WXBizDataCrypt:
def __init__(self, appid, session_key):
self.appid = appid
self.session_key = b64decode(session_key)
def decrypt(self, encrypted_data, iv):
# AES解密
try:
cipher = AES.new(self.session_key, AES.MODE_CBC, b64decode(iv))
decrypted = json.loads(self._unpad(cipher.decrypt(b64decode(encrypted_data))))
if decrypted['watermark']['appid'] != self.appid:
raise Exception('Invalid Buffer')
return decrypted
except Exception as e:
raise Exception('Failed to decrypt data')
def _unpad(self, s):
try:
return s[:-ord(s[len(s) - 1:])]
except Exception as e:
print("Error during unpadding:", e)
return s # 返回原始数据以便进一步分析错误

0
MyWeb/__init__.py Normal file
View File

3
MyWeb/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
MyWeb/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MywebConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'MyWeb'

13
MyWeb/decorators.py Normal file
View File

@ -0,0 +1,13 @@
from django.shortcuts import redirect
from functools import wraps
def admin_login_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# 这里使用 'admin_id' 作为登录状态的标记
if 'admin_id' not in request.session:
# 用户未登录,重定向到登录页面
return redirect('admin_login')
# 用户已登录,执行原始视图函数
return view_func(request, *args, **kwargs)
return _wrapped_view

3
MyWeb/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

27
MyWeb/urls.py Normal file
View File

@ -0,0 +1,27 @@
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.admin_login, name='admin_login'),
path('admin_dashboard/', views.admin_dashboard, name='admin_dashboard'),
path('admin_logout/', views.admin_logout, name='admin_logout'),
path('main/', views.main, name='main'),
path('miniapp_users/', views.miniapp_users, name='miniapp_users'),
path('wechat_users/', views.wechat_users, name='wechat_users'),
path('other_sources/', views.other_sources, name='other_sources'),
path('site_admins/', views.site_admins, name='site_admins'),
path('video_extraction_records/', views.video_extraction_records, name='video_extraction_records'),
path('copy_extraction_records/', views.copy_extraction_records, name='copy_extraction_records'),
path('ai_api_usage/', views.ai_api_usage, name='ai_api_usage'),
path('website_settings/', views.website_settings, name='website_settings'),
path('miniapp_settings/', views.miniapp_settings, name='miniapp_settings'),
path('copywriting_library/', views.copywriting_library, name='copywriting_library'),
path('article_records/', views.article_records, name='article_records'),
path('material_library/', views.material_library, name='material_library'),
path('delete-similar-texts/', views.delete_similar_texts_view, name='delete_similar_texts'),
path('redemption-cards/', views.redemption_card_list, name='redemption_card_list'),
path('membership-types/', views.membership_type_list, name='membership_type_list'),
path('transaction-logs/', views.transaction_log_list, name='transaction_log_list'),
]

609
MyWeb/views.py Normal file
View File

@ -0,0 +1,609 @@
import hashlib
import json
import random
import time
import uuid
from datetime import datetime
from django.db.models import Q
from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
from django.contrib.auth.hashers import check_password
from MyApi.models import Administrator, Copywriting, User, FriendRequest, ApiCallLog, MembershipType, RedemptionCard, \
TransactionLog
from django.contrib.auth import login as django_login, logout as django_logout, login
from django.shortcuts import render, redirect
from .decorators import admin_login_required
from django.contrib.auth import logout
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db import transaction
import Levenshtein
from urllib.parse import unquote, quote
def admin_login(request):
# 检查是否已登录
if 'admin_id' in request.session:
return redirect('admin_dashboard') # 使用视图的名称执行重定向
if request.method == 'POST':
data = json.loads(request.body)
username = data.get('username')
password = data.get('password')
try:
user = Administrator.objects.get(username=username)
except Administrator.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '用户不存在'})
# 检查密码是否正确
if user and check_password(password, user.password):
if user.is_active:
# 登录成功,设置会话
request.session['role'] = user.role
request.session['admin_id'] = user.id
return JsonResponse({'status': 'success', 'message': '登录成功'})
else:
return JsonResponse({'status': 'error', 'message': '用户已被禁用'})
else:
return JsonResponse({'status': 'error', 'message': '密码错误'})
else:
# 非POST请求返回登录页面
return render(request, 'login.html')
@admin_login_required
def admin_dashboard(request):
# 从session获取管理员ID
admin_id = request.session.get('admin_id')
# 使用管理员ID查询管理员信息
try:
admin = Administrator.objects.get(id=admin_id)
except Administrator.DoesNotExist:
# 如果没有找到管理员,可以重定向到登录页面或显示错误信息
return HttpResponse('管理员不存在', status=404)
# 将管理员信息添加到上下文中
# context = {
# 'admin_username': admin.username,
# 'admin_role': admin.role,
# # 添加更多管理员信息到上下文中,根据需要
# }
return render(request, 'index.html', {'username':admin.username})
def admin_logout(request):
# 登出用户
logout(request)
# 重定向到登录页面,或者你希望用户登出后看到的页面
return redirect('admin_login')
@admin_login_required
# 小程序用户管理页面
def miniapp_users(request):
if request.method == "POST":
data = json.loads(request.body)
action = data.get('action')
if action == 'fetch':
# 分页获取小程序用户数据
users_list = User.objects.all().order_by('-created_at')
page = data.get('page', 1)
paginator = Paginator(users_list, 10) # 每页显示10条记录
try:
users = paginator.page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
users_data = list(users.object_list.values(
'openid', 'wxid','wechat_number', 'nickname', 'gender', 'region', 'email',
'phone', 'scene', 'is_member', 'member_start_time',
'member_end_time', 'is_active', 'usage_count', 'created_at'
))
return JsonResponse({
'data': users_data,
'code': 0,
'count': paginator.count,
'msg': ''
})
elif action == 'add':
print('添加')
elif action == 'edit':
# 编辑用户信息
user_id = data.get('id')
try:
user = User.objects.get(id=user_id)
user.wxid = data.get('wxid', user.wxid)
user.nickname = data.get('nickname', user.nickname)
user.gender = data.get('gender', user.gender)
user.region = data.get('region', user.region)
user.email = data.get('email', user.email)
user.phone = data.get('phone', user.phone)
user.scene = data.get('scene', user.scene)
user.is_member = data.get('is_member', user.is_member)
user.member_start_time = data.get('member_start_time', user.member_start_time)
user.member_end_time = data.get('member_end_time', user.member_end_time)
user.is_active = data.get('is_active', user.is_active)
user.usage_count = data.get('usage_count', user.usage_count)
user.save()
return JsonResponse({'status': 'success', 'msg': '用户信息更新成功'})
except User.DoesNotExist:
return JsonResponse({'status': 'error', 'msg': '用户不存在'})
elif action == 'delete':
# 删除用户
user_id = data.get('id')
User.objects.filter(id=user_id).delete()
return JsonResponse({'status': 'success', 'msg': '用户删除成功'})
return render(request, 'miniapp_users.html')
@admin_login_required
# 微信用户管理页面
def wechat_users(request):
if request.method == 'POST':
data = json.loads(request.body)
action = data.get('action')
if action == 'fetch':
# 处理获取好友请求记录的逻辑
page_number = data.get('page', 1)
limit = data.get('limit', 10)
search_keyword = data.get('search', '')
friend_requests_query = FriendRequest.objects.filter(
Q(fromnickname__icontains=search_keyword) |
Q(alias__icontains=search_keyword) |
Q(fromusername__icontains=search_keyword)
).order_by('-time')
paginator = Paginator(friend_requests_query, limit)
try:
friend_requests_page = paginator.page(page_number)
except PageNotAnInteger:
friend_requests_page = paginator.page(1)
except EmptyPage:
friend_requests_page = paginator.page(paginator.num_pages)
friend_requests_list = list(friend_requests_page.object_list.values(
'id', 'fromusername', 'alias', 'fromnickname', 'country',
'province', 'city', 'sex', 'scene', 'time', 'towxid'
))
return JsonResponse({
'code': 0,
'msg': '',
'count': paginator.count,
'data': friend_requests_list
})
elif action == 'add':
# 处理添加好友请求记录的逻辑
pass # 实现添加逻辑
elif action == 'edit':
# 处理编辑好友请求记录的逻辑
pass # 实现编辑逻辑
elif action == 'delete':
# 处理删除好友请求记录的逻辑
fr_id = data.get('id')
FriendRequest.objects.filter(id=fr_id).delete()
return JsonResponse({'status': 'success', 'msg': '记录删除成功'})
return render(request, 'wechat_users.html')
@admin_login_required
# 其他来源用户管理页面
def other_sources(request):
# 实现具体逻辑
return render(request, 'other_sources.html')
@admin_login_required
# 网站管理员管理页面
def site_admins(request):
# 实现具体逻辑
return render(request, 'site_admins.html')
@admin_login_required
# 视频提取记录管理页面
def video_extraction_records(request):
# 实现具体逻辑
return render(request, 'video_extraction_records.html')
@admin_login_required
# 文案提取记录管理页面
def copy_extraction_records(request):
# 实现具体逻辑
return render(request, 'copy_extraction_records.html')
@admin_login_required
# AI接口使用情况页面
def ai_api_usage(request):
# 实现具体逻辑
return render(request, 'ai_api_usage.html')
@admin_login_required
# 网站设置页面
def website_settings(request):
# 实现具体逻辑
return render(request, 'website_settings.html')
@admin_login_required
# 小程序设置页面
def miniapp_settings(request):
# 实现具体逻辑
return render(request, 'miniapp_settings.html')
@admin_login_required
# 后台主页
def main(request):
# 实现具体逻辑
return render(request, 'main.html')
# 文案库页面
@admin_login_required
def copywriting_library(request):
if request.method == "POST":
print(request.body)
data = json.loads(request.body)
action = data.get('action', '')
print(data)
if action == 'fetch':
# 分页参数
page = data.get('page', 1)
limit = data.get('limit', 10)
search = data.get('search', '')
search = quote(search)
# 过滤和排序Q(text_content__icontains=search_query)
queryset = Copywriting.objects.filter(Q(text_content__icontains=search)).order_by('-added_time')
paginator = Paginator(queryset, limit)
try:
copywritings = paginator.page(page)
except PageNotAnInteger:
copywritings = paginator.page(1)
except EmptyPage:
copywritings = paginator.page(paginator.num_pages)
copywriting_list = list(
copywritings.object_list.values('id', 'text_content', 'source', 'popularity','tag','is_approved', 'added_time'))
return JsonResponse({
"code": 0,
"msg": "",
"count": paginator.count,
"data": copywriting_list
})
elif action == 'add':
# 添加文案
text_content = data.get('text_content', '')
source = data.get('source', '')
tag = data.get('tag', '')
popularity = data.get('popularity', 0)
copywriting = Copywriting(
text_content=text_content,
source=source,
tag=tag,
popularity=popularity
)
copywriting.save()
return JsonResponse({"status": "success", "msg": "文案添加成功"})
elif action == 'batch-delete':
# 批量删除文案
ids = data.get('ids', [])
Copywriting.objects.filter(id__in=ids).delete()
return JsonResponse({"status": "success", "msg": "文案批量删除成功"})
elif action == 'delete':
# 批量删除文案
id = data.get('id')
Copywriting.objects.filter(id=id).delete()
return JsonResponse({"status": "success", "msg": "文案批量删除成功"})
elif action == 'edit':
# 获取请求中的数据
copywriting_id = data.get('id')
text_content = data.get('text_content')
source = data.get('source')
tag = data.get('tag')
try:
# 根据ID查找要编辑的文案实例
copywriting = Copywriting.objects.get(id=copywriting_id)
# 更新文案实例的字段
copywriting.text_content = text_content
copywriting.source = source
copywriting.tag = tag
copywriting.save() # 保存更改到数据库
return JsonResponse({'status': 'success', 'message': '文案更新成功'})
except Copywriting.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '文案未找到'})
elif action == 'approve':
try:
copywriting_id = data.get('id')
copywriting = Copywriting.objects.get(id=copywriting_id)
copywriting.is_approved = 1
copywriting.save() # 保存更改到数据库
return JsonResponse({'status': 'success', 'message': '文案审核成功'})
except Copywriting.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '文案未找到'})
# 对于非POST请求或者没有指定操作的POST请求返回页面
return render(request, 'copywriting_library.html')
@admin_login_required
# 文章记录页面
def article_records(request):
# 这里实现具体的视图逻辑
return render(request, 'article_records.html')
@admin_login_required
# 素材库页面
def material_library(request):
# 这里实现具体的视图逻辑
return render(request, 'material_library.html')
#删除重复文案
def get_similar_texts(threshold=0.8):
# 假设threshold为相似度阈值
all_texts = Copywriting.objects.filter(is_approved=0) # 仅考虑已审核的文案
print(len(all_texts))
to_delete = []
# 将查询到的文案内容解码
decoded_texts = [(text, unquote(text.text_content)) for text in all_texts]
for i, (text1, decoded_content1) in enumerate(decoded_texts):
for text2, decoded_content2 in decoded_texts[i + 1:]:
# 计算解码后的文本之间的相似度
if Levenshtein.ratio(decoded_content1, decoded_content2) > threshold:
# 如果相似度大于阈值,选择热度较低的文案删除
if text1.popularity <= text2.popularity:
to_delete.append(text1)
else:
to_delete.append(text2)
return set(to_delete) # 使用集合避免重复
@transaction.atomic
def delete_similar_texts():
to_delete = get_similar_texts()
deleted_count = len(to_delete) # 计算删除对象的数量
for text in to_delete:
text.delete() # 删除操作
return deleted_count # 返回删除的行数
def delete_similar_texts_view(request):
deleted_count = delete_similar_texts()
print(f"Deleted {deleted_count} similar texts.")
# 返回结果
return JsonResponse({'message': f'Deleted {deleted_count} similar texts.'})
def generate_redemption_code(prefix):
# 生成唯一的 UUID
unique_id = uuid.uuid4()
# 获取当前时间戳
current_timestamp = int(time.time())
# 使用 MD5 算法生成散列值
hash_object = hashlib.md5(unique_id.bytes + str(current_timestamp).encode())
hash_hex = hash_object.hexdigest()
# 将前缀和散列值的前8位拼接起来作为卡密
redemption_code = f"{prefix}{hash_hex[:8]}"
return redemption_code
#卡密管理
@admin_login_required
def redemption_card_list(request):
if request.method == "POST":
data = json.loads(request.body)
action = data.get('action', '')
if action == 'fetch':
# 获取分页参数
page = int(data.get('page', 1))
limit = int(data.get('limit', 10))
search = data.get('search', '')
search = quote(search)
# 过滤和排序
queryset = RedemptionCard.objects.filter().order_by('-card_creation_time')
paginator = Paginator(queryset, limit)
try:
redemptionCard = paginator.page(page)
except PageNotAnInteger:
redemptionCard = paginator.page(1)
except EmptyPage:
redemptionCard = paginator.page(paginator.num_pages)
copywriting_list = list(
redemptionCard.object_list.values('id', 'code', 'card_type', 'created_date', 'expiry_date', 'is_used',
'used_by_openid','used_by_nickname','card_creation_time','validity_period'))
return JsonResponse({
"code": 0,
"message": "ok",
"count": paginator.count,
"data": copywriting_list
})
elif action == 'edit':
try:
redemptionCard_id = data.get('id')
redemptionCard = RedemptionCard.objects.get(id=redemptionCard_id)
# 更新文案
# 这里添加更新文案的逻辑
return JsonResponse({'status': 'success', 'message': '更新成功'})
except RedemptionCard.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '卡密未找到'})
elif action == 'add':
try:
# 解析请求数据
code_prefix = data.get('code_prefix', '')
card_type = data.get('card_type', '')
quantity = int(data.get('quantity', 0))
expiry_date = data.get('expiry_date', '')
validity_period = int(data.get('validity_period',0))
# 添加卡密逻辑
for _ in range(quantity):
card = RedemptionCard.objects.create(
code=generate_redemption_code(code_prefix),
card_type=card_type,
expiry_date=expiry_date,
validity_period=validity_period
)
return JsonResponse({'status': 'success', 'message': '卡密添加成功'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)})
elif action == 'delete':
try:
redemptionCard_id = data.get('id')
redemptionCard = RedemptionCard.objects.get(id=redemptionCard_id)
redemptionCard.delete() # 删除单个卡密记录
return JsonResponse({'status': 'success', 'message': '卡密删除成功'})
except RedemptionCard.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '卡密未找到'})
elif action == 'batch_delete':
try:
ids = data.get('ids', [])
RedemptionCard.objects.filter(id__in=ids).delete() # 批量删除卡密记录
return JsonResponse({'status': 'success', 'message': '批量删除卡密成功'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)})
else:
return JsonResponse({
"code": 400,
"msg": "Invalid action parameter.",
})
return render(request, 'redemption_card.html')
# 会员配置
@admin_login_required
def membership_type_list(request):
if request.method == 'POST':
data = json.loads(request.body)
action = data['action']
if action == 'fetch': # 读取会员卡类型列表
is_quota = data.get('is_quota', None)
if is_quota is not None:
membership_types = MembershipType.objects.filter(is_quota=is_quota)
else:
membership_types = MembershipType.objects.all()
# 将会员卡类型列表序列化为JSON格式
membership_types_data = [
{
'id': m.id,
'type': m.type,
'title': m.title,
'description': m.description,
'price': m.price,
'duration_days': m.duration_days,
'coins': m.coins,
'is_quota': m.is_quota
} for m in membership_types
]
return JsonResponse({"code": 0, 'data': membership_types_data})
elif action == 'add': # 添加会员卡类型
# 获取前端传递过来的数据
membership_type = MembershipType(
type=data.get('type'),
title=data.get('title'),
description=data.get('description'),
price=data.get('price'),
duration_days=data.get('duration_days'),
coins=data.get('coins'),
is_quota=data.get('is_quota')
)
membership_type.save()
return JsonResponse({'status': 'success', 'message': '会员卡类型添加成功'})
elif action == 'edit': # 编辑会员卡类型
id = int(data['id'])
# 根据id获取要修改的MembershipType对象
membership_type = MembershipType.objects.get(id=id)
# 获取前端传递过来的数据
membership_type.title = data.get('title')
membership_type.description = data.get('description')
membership_type.price = data.get('price')
membership_type.duration_days = data.get('duration_days')
membership_type.coins = data.get('coins')
membership_type.is_quota = data.get('is_quota')
membership_type.save()
return JsonResponse({'status': 'success', 'message': '会员卡类型修改成功'})
elif action == 'delete': # 删除会员卡类型
id = int(data['id'])
# 根据id删除对应的MembershipType对象
MembershipType.objects.filter(id=id).delete()
return JsonResponse({'status': 'success', 'message': '会员卡类型删除成功'})
return render(request, 'membership-types.html')
# 订单记录
@admin_login_required
def transaction_log_list(request):
if request.method == "POST":
data = json.loads(request.body)
action = data.get('action', '')
if action == 'fetch':
page = data.get('page', 1)
limit = data.get('limit', 10)
queryset = TransactionLog.objects.all().order_by('-created_at')
paginator = Paginator(queryset, limit)
try:
transaction_logs = paginator.page(page)
except PageNotAnInteger:
transaction_logs = paginator.page(1)
except EmptyPage:
transaction_logs = paginator.page(paginator.num_pages)
transaction_log_list = list(
transaction_logs.object_list.values(
'id',
'transaction_no',
'transaction_status',
'user_openid',
'transaction_type',
'transaction_amount',
'remark',
'created_at',
'updated_at'
)
)
return JsonResponse({
"code": 0,
"msg": "",
"count": paginator.count,
"data": transaction_log_list
})
elif action == 'delete':
try:
id = data.get('id')
transaction_log = TransactionLog.objects.get(id=id)
transaction_log.delete()
return JsonResponse({'status': 'success', 'message': '订单记录删除成功'})
except TransactionLog.DoesNotExist:
return JsonResponse({'status': 'error', 'message': '订单记录未找到'})
elif action == 'batch_delete':
try:
ids = data.get('ids', [])
TransactionLog.objects.filter(id__in=ids).delete()
return JsonResponse({'status': 'success', 'message': '批量删除订单记录成功'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)})
return render(request, 'transaction-logs.html')

22
manage.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'website.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

242
static/css/style.css Normal file
View File

@ -0,0 +1,242 @@
/* 清除浏览器默认边距
使边框和内边距的值包含在元素的width和height内 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 使用flex布局让内容垂直和水平居中 */
section {
/* 相对定位 */
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/* linear-gradient() 函数用于创建一个表示两种或多种颜色线性渐变的图片 */
background: linear-gradient(to bottom, #f1f4f9, #dff1ff);
}
/* 背景颜色 */
section .color {
/* 绝对定位 */
position: absolute;
/* 使用filter(滤镜) 属性,给图像设置高斯模糊*/
filter: blur(200px);
}
/* :nth-child(n) 选择器匹配父元素中的第 n 个子元素 */
section .color:nth-child(1) {
top: -350px;
width: 600px;
height: 600px;
background: #ff359b;
}
section .color:nth-child(2) {
bottom: -150px;
left: 100px;
width: 500px;
height: 500px;
background: #fffd87;
}
section .color:nth-child(3) {
bottom: 50px;
right: 100px;
width: 500px;
height: 500px;
background: #00d2ff;
}
.box {
position: relative;
}
/* 背景圆样式 */
.box .circle {
position: absolute;
background: rgba(255, 255, 255, 0.1);
/* backdrop-filter属性为一个元素后面区域添加模糊效果 */
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
/* 使用filter(滤镜) 属性改变颜色
hue-rotate(deg) 给图像应用色相旋转
calc() 函数用于动态计算长度值
var() 函数调用自定义的CSS属性值x*/
filter: hue-rotate(calc(var(--x) * 70deg));
/* 调用动画animate需要10s完成动画
linear表示动画从头到尾的速度是相同的
infinite指定动画应该循环播放无限次*/
animation: animate 10s linear infinite;
/* 动态计算动画延迟几秒播放 */
animation-delay: calc(var(--x) * -1s);
}
/* 背景圆动画 */
@keyframes animate {
0%, 100%{
transform: translateY(-50px);
}
50% {
transform: translateY(50px);
}
}
.box .circle:nth-child(1) {
top: -50px;
right: -60px;
width: 100px;
height: 100px;
}
.box .circle:nth-child(2) {
top: 150px;
left: -100px;
width: 120px;
height: 120px;
z-index: 2;
}
.box .circle:nth-child(3) {
bottom: 50px;
right: -60px;
width: 80px;
height: 80px;
z-index: 2;
}
.box .circle:nth-child(4) {
bottom: -80px;
left: 100px;
width: 60px;
height: 60px;
}
.box .circle:nth-child(5) {
top: -80px;
left: 140px;
width: 60px;
height: 60px;
}
/* 登录框样式 */
.container {
position: relative;
width: 400px;
min-height: 400px;
background: rgba(255, 255, 255, 0.1);
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.form {
position: relative;
width: 100%;
height: 100%;
padding: 50px;
}
/* 登录标题样式 */
.form h2 {
position: relative;
color: #333;
font-size: 24px;
font-weight: 600;
letter-spacing: 5px;
margin-bottom: 30px;
cursor: pointer;
}
/* 登录标题的下划线样式 */
.form h2::before {
content: "";
position: absolute;
left: 0;
bottom: -10px;
width: 0px;
height: 3px;
background: #fff;
transition: 0.5s;
}
.form h2:hover:before {
width: 53px;
}
.form .inputBox {
width: 100%;
margin-top: 20px;
}
/* 输入框样式 */
.form .inputBox input {
width: 100%;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.2);
outline: none;
border: none;
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
font-size: 16px;
letter-spacing: 1px;
color: #666;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.form .inputBox input::placeholder {
color: #fff;
}
/* 登录按钮样式 */
.form .inputBox input[type="submit"] {
background: #fff;
color: #666;
max-width: 100px;
margin-bottom: 20px;
font-weight: 600;
cursor: pointer;
}
.forget {
margin-top: 6px;
color: #333;
letter-spacing: 1px;
}
.forget a {
color: #666;
font-weight: 600;
text-decoration: none;
}
.radio input{
margin-top:15px;
}

BIN
static/ding.mp3 Normal file

Binary file not shown.

BIN
static/img/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

3
static/js/axios.js Normal file

File diff suppressed because one or more lines are too long

1589
static/js/china.js Normal file

File diff suppressed because it is too large Load Diff

8
static/js/echarts-wordcloud.min.js vendored Normal file

File diff suppressed because one or more lines are too long

98075
static/js/echarts.js Normal file

File diff suppressed because it is too large Load Diff

45
static/js/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
static/js/jquery-2.1.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
static/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
/** layui-v2.4.5 MIT License By https://www.layui.com */
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Some files were not shown because too many files have changed in this diff Show More