第一次提交:小程序前端最新版本
This commit is contained in:
31
.eslintrc.js
Normal file
31
.eslintrc.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Eslint config file
|
||||||
|
* Documentation: https://eslint.org/docs/user-guide/configuring/
|
||||||
|
* Install the Eslint extension before using this feature.
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
ecmaFeatures: {
|
||||||
|
modules: true,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
wx: true,
|
||||||
|
App: true,
|
||||||
|
Page: true,
|
||||||
|
getCurrentPages: true,
|
||||||
|
getApp: true,
|
||||||
|
Component: true,
|
||||||
|
requirePlugin: true,
|
||||||
|
requireMiniProgram: true,
|
||||||
|
},
|
||||||
|
// extends: 'eslint:recommended',
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
miniprogram_npm/
|
||||||
|
project.private.config.json
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
30
.idea/a_long_dianjing.iml
generated
Normal file
30
.idea/a_long_dianjing.iml
generated
Normal 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="a_long_dianjing/settings.py" />
|
||||||
|
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<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="jdk" jdkName="Python 3.13 (a_long_dianjing)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/../a_long_dianjing\templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.13 (a_long_dianjing)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (a_long_dianjing)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal 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/a_long_dianjing.iml" filepath="$PROJECT_DIR$/.idea/a_long_dianjing.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
3
.vs/ProjectSettings.json
Normal file
3
.vs/ProjectSettings.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"CurrentProjectSetting": null
|
||||||
|
}
|
||||||
7
.vs/VSWorkspaceState.json
Normal file
7
.vs/VSWorkspaceState.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"SelectedNode": "\\app.js",
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
.vs/a_long_dianjing/v17/.wsuo
Normal file
BIN
.vs/a_long_dianjing/v17/.wsuo
Normal file
Binary file not shown.
37
.vs/a_long_dianjing/v17/DocumentLayout.json
Normal file
37
.vs/a_long_dianjing/v17/DocumentLayout.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\",
|
||||||
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js||{14D17961-FE51-464D-9111-C4AF11D7D99A}",
|
||||||
|
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:app.js||{14D17961-FE51-464D-9111-C4AF11D7D99A}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 0,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": "app.js",
|
||||||
|
"DocumentMoniker": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js",
|
||||||
|
"RelativeDocumentMoniker": "app.js",
|
||||||
|
"ToolTip": "C:\\\u5FAE\u4FE1\u5F00\u53D1\u5DE5\u5177\\\u4EE3\u7801\\a_long_dianjing\\app.js",
|
||||||
|
"RelativeToolTip": "app.js",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001646|",
|
||||||
|
"WhenOpened": "2026-01-30T05:12:55.655Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 GoEasy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
2
_init_.py
Normal file
2
_init_.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 3. 在项目的 `__init__.py` 文件(与manage.py同级)中,添加两行代码
|
||||||
|
# 文件路径:a_long_dianjing/__init__.py
|
||||||
8
a_long_dianjing/__init__.py
Normal file
8
a_long_dianjing/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
阿龙电竞 - Django项目初始化
|
||||||
|
确保Celery在Django启动时自动加载
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app',)
|
||||||
BIN
a_long_dianjing/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
a_long_dianjing/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
a_long_dianjing/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/celery.cpython-312.pyc
Normal file
BIN
a_long_dianjing/__pycache__/celery.cpython-312.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/celery.cpython-313.pyc
Normal file
BIN
a_long_dianjing/__pycache__/celery.cpython-313.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/settings.cpython-313.pyc
Normal file
BIN
a_long_dianjing/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/urls.cpython-313.pyc
Normal file
BIN
a_long_dianjing/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
a_long_dianjing/__pycache__/wsgi.cpython-313.pyc
Normal file
BIN
a_long_dianjing/__pycache__/wsgi.cpython-313.pyc
Normal file
Binary file not shown.
16
a_long_dianjing/asgi.py
Normal file
16
a_long_dianjing/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for a_long_dianjing project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
115
a_long_dianjing/celery.py
Normal file
115
a_long_dianjing/celery.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
"""
|
||||||
|
阿龙电竞 - Celery定时任务主配置
|
||||||
|
生产环境就绪版本,支持精确延时任务和周期性任务
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from celery import Celery
|
||||||
|
from celery.schedules import crontab
|
||||||
|
|
||||||
|
# 设置Django默认设置模块
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
|
||||||
|
|
||||||
|
# 创建Celery应用实例
|
||||||
|
app = Celery('a_long_dianjing')
|
||||||
|
|
||||||
|
# 从Django settings中加载Celery配置(CELERY_前缀)
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# 自动发现所有已注册app中的tasks.py文件
|
||||||
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
|
# 配置周期性任务(Celery Beat Schedule)
|
||||||
|
app.conf.beat_schedule = {
|
||||||
|
# 🔥 核心任务:订单调度器(每2分钟运行)
|
||||||
|
'dispatch_pending_orders': {
|
||||||
|
'task': 'dingdan.tasks.dispatch_pending_orders',
|
||||||
|
'schedule': crontab(minute='*/2'),
|
||||||
|
'options': {'queue': 'order_tasks', 'priority': 5},
|
||||||
|
},
|
||||||
|
|
||||||
|
# 🔥 补偿检查任务(每5分钟运行)
|
||||||
|
'check_order_expire_task': {
|
||||||
|
'task': 'dingdan.tasks.check_order_expire_task',
|
||||||
|
'schedule': crontab(minute='*/5'),
|
||||||
|
'options': {'queue': 'order_tasks', 'priority': 3},
|
||||||
|
},
|
||||||
|
# 1. 每日凌晨0点执行 - 清零任务
|
||||||
|
'daily_reset_task': {
|
||||||
|
'task': 'yonghu.tasks.daily_reset_task',
|
||||||
|
'schedule': crontab(hour=0, minute=0), # 每天0点
|
||||||
|
'options': {
|
||||||
|
'queue': 'periodic_tasks',
|
||||||
|
'priority': 5
|
||||||
|
},
|
||||||
|
'args': (),
|
||||||
|
'kwargs': {}
|
||||||
|
},
|
||||||
|
|
||||||
|
# 2. 每月1日凌晨0点执行 - 月度清零任务
|
||||||
|
'monthly_reset_task': {
|
||||||
|
'task': 'yonghu.tasks.monthly_reset_task',
|
||||||
|
'schedule': crontab(hour=0, minute=0, day_of_month=1), # 每月1日0点
|
||||||
|
'options': {
|
||||||
|
'queue': 'periodic_tasks',
|
||||||
|
'priority': 5
|
||||||
|
},
|
||||||
|
'args': (),
|
||||||
|
'kwargs': {}
|
||||||
|
},
|
||||||
|
|
||||||
|
# 3. 每5分钟检查一次待处理订单 - 作为补偿机制
|
||||||
|
'check_order_expire_task': {
|
||||||
|
'task': 'dingdan.tasks.check_order_expire_task',
|
||||||
|
'schedule': crontab(minute='*/5'), # 每5分钟
|
||||||
|
'options': {
|
||||||
|
'queue': 'order_tasks',
|
||||||
|
'priority': 3
|
||||||
|
},
|
||||||
|
'args': (),
|
||||||
|
'kwargs': {}
|
||||||
|
},
|
||||||
|
|
||||||
|
# 4. 每天凌晨0点5分清理收支记录
|
||||||
|
'daily_sz_reset_task': {
|
||||||
|
'task': 'peizhi.tasks.daily_sz_reset_task',
|
||||||
|
'schedule': crontab(hour=0, minute=5), # 每天0点5分
|
||||||
|
'options': {
|
||||||
|
'queue': 'periodic_tasks',
|
||||||
|
'priority': 4
|
||||||
|
},
|
||||||
|
'args': (),
|
||||||
|
'kwargs': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 时区设置
|
||||||
|
app.conf.timezone = 'Asia/Shanghai'
|
||||||
|
app.conf.enable_utc = True
|
||||||
|
|
||||||
|
# 队列路由配置
|
||||||
|
app.conf.task_routes = {
|
||||||
|
'dingdan.tasks.*': {'queue': 'order_tasks'},
|
||||||
|
'yonghu.tasks.*': {'queue': 'periodic_tasks'},
|
||||||
|
'peizhi.tasks.*': {'queue': 'periodic_tasks'},
|
||||||
|
}
|
||||||
|
|
||||||
|
# 任务序列化
|
||||||
|
app.conf.accept_content = ['json']
|
||||||
|
app.conf.task_serializer = 'json'
|
||||||
|
app.conf.result_serializer = 'json'
|
||||||
|
|
||||||
|
# 任务超时设置
|
||||||
|
app.conf.task_time_limit = 300 # 任务最大执行时间300秒
|
||||||
|
app.conf.task_soft_time_limit = 240 # 软超时240秒
|
||||||
|
|
||||||
|
# Worker并发设置
|
||||||
|
app.conf.worker_concurrency = 4
|
||||||
|
app.conf.worker_prefetch_multiplier = 1
|
||||||
|
|
||||||
|
# 任务确认设置
|
||||||
|
app.conf.task_acks_late = True
|
||||||
|
app.conf.worker_disable_rate_limits = True
|
||||||
|
|
||||||
|
# 结果过期时间
|
||||||
|
app.conf.result_expires = 3600 # 任务结果保留1小时
|
||||||
114
a_long_dianjing/management/commands/sync_cross_order_status.py
Normal file
114
a_long_dianjing/management/commands/sync_cross_order_status.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.conf import settings
|
||||||
|
from dingdan.models import Dingdan
|
||||||
|
from peizhi.models import Club # 你的俱乐部模型,请确认类名
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '同步我方派单跨平台订单状态(仅进行中订单,失败直接跳过,不重试)'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--concurrency', type=int, default=3, help='并发数(默认3)')
|
||||||
|
parser.add_argument('--batch', type=int, default=0, help='处理前N单(0=全部)')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
concurrency = options['concurrency']
|
||||||
|
batch = options['batch']
|
||||||
|
|
||||||
|
# 查询待同步订单
|
||||||
|
orders = Dingdan.objects.filter(
|
||||||
|
zhuangtai=2,
|
||||||
|
dispatch_type=1,
|
||||||
|
is_cross=1,
|
||||||
|
partner_club_id__isnull=False,
|
||||||
|
partner_order_id__isnull=False
|
||||||
|
).only('dingdan_id', 'partner_order_id', 'partner_club_id', 'zhuangtai')
|
||||||
|
|
||||||
|
total = orders.count()
|
||||||
|
self.stdout.write(f'待处理订单总数: {total}')
|
||||||
|
if batch > 0:
|
||||||
|
orders = orders[:batch]
|
||||||
|
self.stdout.write(f'本次仅处理前 {batch} 单')
|
||||||
|
|
||||||
|
SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', 'xK9mP2vL8qW5rT7y')
|
||||||
|
session = requests.Session()
|
||||||
|
session.headers.update({'Content-Type': 'application/json'})
|
||||||
|
|
||||||
|
# 预加载域名映射
|
||||||
|
domain_map = {}
|
||||||
|
for club in Club.objects.all():
|
||||||
|
if club.partner_club_id and club.partner_domain:
|
||||||
|
domain_map[club.partner_club_id] = club.partner_domain
|
||||||
|
|
||||||
|
updated = success = skip = fail = 0
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
def process_one(order):
|
||||||
|
# 获取对方域名
|
||||||
|
domain = domain_map.get(order.partner_club_id)
|
||||||
|
if not domain:
|
||||||
|
try:
|
||||||
|
club = Club.objects.filter(partner_club_id=order.partner_club_id).first()
|
||||||
|
if club and club.partner_domain:
|
||||||
|
domain = club.partner_domain
|
||||||
|
domain_map[order.partner_club_id] = domain
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not domain:
|
||||||
|
return ('skip', '无域名')
|
||||||
|
|
||||||
|
url = domain.rstrip('/') + '/houtai/kptxwztjb'
|
||||||
|
payload = {
|
||||||
|
'token': SYNC_TOKEN,
|
||||||
|
'partner_order_id': order.partner_order_id,
|
||||||
|
'our_order_id': order.dingdan_id,
|
||||||
|
'club_id': order.partner_club_id
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = session.post(url, json=payload, timeout=5)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
if data.get('code') == 0:
|
||||||
|
st = data['data']['status']
|
||||||
|
if st in (5, 8):
|
||||||
|
order.zhuangtai = st
|
||||||
|
order.save(update_fields=['zhuangtai'])
|
||||||
|
return ('updated', st)
|
||||||
|
return ('success', st)
|
||||||
|
else:
|
||||||
|
return ('skip', data.get('msg', '对方业务错误'))
|
||||||
|
else:
|
||||||
|
# 429、404、500 等全部跳过
|
||||||
|
return ('skip', f'HTTP {resp.status_code}')
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return ('skip', '超时')
|
||||||
|
except Exception as e:
|
||||||
|
return ('skip', str(e))
|
||||||
|
|
||||||
|
# 并发执行
|
||||||
|
with ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||||
|
future_map = {executor.submit(process_one, o): o for o in orders}
|
||||||
|
for future in as_completed(future_map):
|
||||||
|
order = future_map[future]
|
||||||
|
try:
|
||||||
|
res, detail = future.result()
|
||||||
|
if res == 'updated':
|
||||||
|
updated += 1
|
||||||
|
success += 1
|
||||||
|
self.stdout.write(f'✅ 更新: {order.dingdan_id} -> {detail}')
|
||||||
|
elif res == 'success':
|
||||||
|
success += 1
|
||||||
|
else:
|
||||||
|
skip += 1
|
||||||
|
# 跳过的订单静默处理,不打印,以免刷屏
|
||||||
|
except Exception as e:
|
||||||
|
skip += 1
|
||||||
|
|
||||||
|
elapsed = time.time() - start
|
||||||
|
self.stdout.write(self.style.SUCCESS(
|
||||||
|
f'完成: 成功{success}(更新{updated}), 跳过{skip}, 耗时{elapsed:.1f}s'
|
||||||
|
))
|
||||||
461
a_long_dianjing/settings.py
Normal file
461
a_long_dianjing/settings.py
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
"""
|
||||||
|
Django settings for a_long_dianjing project.
|
||||||
|
开发环境配置 - 参照旧项目结构精简
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import timedelta
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
SECRET_KEY = '' # 开发环境可暂用,生产必须改
|
||||||
|
DEBUG = True # 开发环境为 True
|
||||||
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1'] # 根据开发前端地址可添加
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# 第三方应用 (根据 requirements.txt 已安装的)
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework_simplejwt',
|
||||||
|
'corsheaders',
|
||||||
|
'django_celery_results',
|
||||||
|
# 你的自定义应用 (按已创建的拼音命名)
|
||||||
|
'celery',
|
||||||
|
'yonghu',
|
||||||
|
'dingdan',
|
||||||
|
'shangpin',
|
||||||
|
'peizhi',
|
||||||
|
'houtai',
|
||||||
|
# 'games', # 【待确认】你提到 games 还没创建,暂不添加
|
||||||
|
'utils',
|
||||||
|
'shangdian',
|
||||||
|
'dengji',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'corsheaders.middleware.CorsMiddleware', # 跨域中间件放最前
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware', # 默认开启,如API无需可注释
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
# 'guanapi.middleware.OpLogMiddleware', # 【待确认】这是旧项目的自定义中间件,新项目需要吗?
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'a_long_dianjing.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / 'templates'],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'a_long_dianjing.wsgi.application'
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# 根据你提供的信息配置 (本地开发,端口3307)
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'NAME': 'a_long_dianjing', # 你创建的数据库名
|
||||||
|
'USER': 'root', # 你的本地MySQL用户名
|
||||||
|
'PASSWORD': 'root', # 你的本地MySQL密码
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': '3307', # 注意端口是3307
|
||||||
|
'OPTIONS': {
|
||||||
|
'charset': 'utf8mb4',
|
||||||
|
'init_command': "SET time_zone='+08:00'", # 保持与旧项目一致
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
LANGUAGE_CODE = 'zh-Hans' # 使用中文
|
||||||
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
USE_I18N = True
|
||||||
|
USE_TZ = False # 保持与旧项目一致,不使用UTC时区
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
# 开发环境静态文件目录,可参照旧项目添加 MEDIA 配置
|
||||||
|
# MEDIA_URL = 'media/'
|
||||||
|
# MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# ==================== 以下为第三方库及自定义配置 ====================
|
||||||
|
# settings.py
|
||||||
|
# 修改SIMPLE_JWT配置
|
||||||
|
SIMPLE_JWT = {
|
||||||
|
'ACCESS_TOKEN_LIFETIME': timedelta(days=36500),
|
||||||
|
'REFRESH_TOKEN_LIFETIME': timedelta(days=36500),
|
||||||
|
'ROTATE_REFRESH_TOKENS': False,
|
||||||
|
'BLACKLIST_AFTER_ROTATION': False,
|
||||||
|
'UPDATE_LAST_LOGIN': False,
|
||||||
|
'ALGORITHM': 'HS256',
|
||||||
|
'SIGNING_KEY': SECRET_KEY,
|
||||||
|
'VERIFYING_KEY': None,
|
||||||
|
'AUDIENCE': None,
|
||||||
|
'ISSUER': None,
|
||||||
|
'JWK_URL': None,
|
||||||
|
'LEEWAY': 0,
|
||||||
|
'AUTH_HEADER_TYPES': ('Bearer',),
|
||||||
|
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
|
||||||
|
# 修改这里:使用yonghuid而不是id
|
||||||
|
'USER_ID_FIELD': 'yonghuid',
|
||||||
|
'USER_ID_CLAIM': 'yonghuid',
|
||||||
|
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
|
||||||
|
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
|
||||||
|
'TOKEN_TYPE_CLAIM': 'token_type',
|
||||||
|
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
|
||||||
|
'JTI_CLAIM': 'jti',
|
||||||
|
}
|
||||||
|
|
||||||
|
# 添加自定义认证后端
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
# 必须指定用户模型(即使它不继承AbstractBaseUser)
|
||||||
|
AUTH_USER_MODEL = 'yonghu.UserMain'
|
||||||
|
|
||||||
|
# DRF配置(也非常简单)
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
|
),
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
),
|
||||||
|
'DEFAULT_THROTTLE_CLASSES': [
|
||||||
|
'rest_framework.throttling.AnonRateThrottle', # 匿名用户限制
|
||||||
|
'rest_framework.throttling.UserRateThrottle', # 登录用户限制
|
||||||
|
],
|
||||||
|
'DEFAULT_THROTTLE_RATES': {
|
||||||
|
'anon': '100/minute', # 匿名用户每分钟100次
|
||||||
|
'user': '1000/minute', # 登录用户每分钟1000次
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# CORS settings
|
||||||
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost:8080", # 假设前端开发服务器地址
|
||||||
|
"http://127.0.0.1:8080",
|
||||||
|
"http://localhost:5174",
|
||||||
|
]
|
||||||
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
CORS_ALLOW_METHODS = (
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"PATCH",
|
||||||
|
"DELETE",
|
||||||
|
"OPTIONS",
|
||||||
|
)
|
||||||
|
CORS_ALLOW_HEADERS = [
|
||||||
|
"accept",
|
||||||
|
"accept-encoding",
|
||||||
|
"authorization",
|
||||||
|
"content-type",
|
||||||
|
"dnt",
|
||||||
|
"origin",
|
||||||
|
"user-agent",
|
||||||
|
"x-csrftoken",
|
||||||
|
"x-requested-with",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 【待确认】自定义用户模型,你需要在 yonghu/models.py 中创建 class Yonghu
|
||||||
|
#AUTH_USER_MODEL = 'yonghu.Yonghu'
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 跨平台订单互通配置 ====================
|
||||||
|
# 接口认证令牌(双方约定,用于简单验证)
|
||||||
|
CROSS_PLATFORM_TOKEN = 'jdjdjshwjqk'
|
||||||
|
|
||||||
|
# 对方平台接口基础URL(需根据实际部署修改)
|
||||||
|
PARTNER_BASE_URL = 'https://partner.com/api'
|
||||||
|
|
||||||
|
# 对方平台各接口路径(可根据实际情况调整,但双方需保持一致)
|
||||||
|
PARTNER_SYNC_ORDER_URL = '/dingdan/partner_sync_order' # 接收派单
|
||||||
|
PARTNER_CHECK_CLAIM_URL = '/dingdan/partner_check_claim' # 抢单询问
|
||||||
|
PARTNER_NOTIFY_CLAIM_URL = '/dingdan/partner_notify_claim' # 抢单成功通知
|
||||||
|
PARTNER_REFUND_NOTIFY_URL = '/dingdan/partner_refund_notify' # 退款通知
|
||||||
|
|
||||||
|
# 我方代表对方发单的商家ID(需在数据库中存在,且具有商家扩展表)
|
||||||
|
AGENT_SHANGJIA_ID = '9106027'
|
||||||
|
|
||||||
|
# 用于验证的会员ID(根据业务约定)
|
||||||
|
CROSS_PLATFORM_MEMBER_ID = 'vip_2025'
|
||||||
|
|
||||||
|
# 默认游戏类型ID(当对方未传递时使用)
|
||||||
|
CROSS_PLATFORM_GAME_TYPE_ID = 1
|
||||||
|
|
||||||
|
# 允许跨平台的商家ID列表(白名单)
|
||||||
|
CROSS_PLATFORM_WHITELIST = ['5438898']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# OSS配置
|
||||||
|
|
||||||
|
|
||||||
|
COS_SECRET_ID =''
|
||||||
|
COS_SECRET_KEY =''
|
||||||
|
COS_BUCKET = ''
|
||||||
|
COS_APPID = '' # 你的腾讯云APPID(julebu-1361527063中的数字部分)
|
||||||
|
|
||||||
|
|
||||||
|
COS_REGION = 'ap-shanghai'
|
||||||
|
|
||||||
|
|
||||||
|
COS_SCHEME = 'https' # 使用HTTPS
|
||||||
|
COS_DOMAIN = f'https://{COS_BUCKET}.cos.{COS_REGION}.myqcloud.com'
|
||||||
|
# settings.py - 在COS配置部分添加
|
||||||
|
#COS_APPID = '1361527063' # 你的腾讯云APPID(julebu-1361527063中的数字部分)
|
||||||
|
COS_STS_DURATION = 7200 # STS临时凭证有效期(秒)
|
||||||
|
|
||||||
|
# ==================== 业务相关配置 (值先空着,结构可保留) ====================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 微信虚拟支付配置
|
||||||
|
VP_OFFER_ID = '你的offer_id' # 虚拟支付应用ID
|
||||||
|
VP_APPKEY = '你的现网AppKey' # 正式环境密钥
|
||||||
|
VP_APPKEY_SANDBOX = '你的沙箱AppKey' # 沙箱环境密钥
|
||||||
|
VP_ENV = 1 # 1=沙箱(测试), 0=正式(上线改这里)
|
||||||
|
|
||||||
|
|
||||||
|
# 微信小程序配置 (开发环境可先留空或使用测试号)
|
||||||
|
|
||||||
|
WEIXIN_APPID = '' # 你的小程序APPID
|
||||||
|
WEIXIN_SECRET = ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WEIXIN_MCHID = '' # 商户号,支付功能需要
|
||||||
|
|
||||||
|
|
||||||
|
WEIXIN_SHANGHUMIYAO = ''# 商户密钥,支付功能需要
|
||||||
|
# 🆕【新增】服务号配置
|
||||||
|
WEIXIN_OFFICIAL_APPID = '' # 服务号APPID
|
||||||
|
WEIXIN_OFFICIAL_SECRET = '填写你的服务号APPSECRET' # 服务号SECRET
|
||||||
|
WEIXIN_OFFICIAL_TOKEN = '填写你的Token' # 服务号Token
|
||||||
|
WEIXIN_OFFICIAL_ENCODING_AES_KEY = '填写你的EncodingAESKey' # 可选
|
||||||
|
WEIXIN_TEMPLATE_ID = '' # 模板ID
|
||||||
|
|
||||||
|
|
||||||
|
# 罚款缴纳微信支付配置
|
||||||
|
FAKUAN_PAY_DESCRIPTION = '罚款缴纳' # 支付描述
|
||||||
|
FAKUAN_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/fk_huitiao' # 微信支付回调地址(根据实际域名调整)
|
||||||
|
|
||||||
|
# 🆕【新增】模板消息配置
|
||||||
|
WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950 # 每分钟最大发送量(留50缓冲)
|
||||||
|
WEIXIN_TEMPLATE_BATCH_SIZE = 100 # 每批发送数量
|
||||||
|
|
||||||
|
WEIXIN_NOTIFY_URL = 'http:api' # 支付回调地址,如:http://你的域名/api/pay/callback/
|
||||||
|
# settings.py 或相应配置文件中
|
||||||
|
WEIXIN_PAY_DESCRIPTION = "网络服务" # 支付描述
|
||||||
|
# 押金充值相关配置
|
||||||
|
YAJIN_PAY_DESCRIPTION = "打手押金充值" # 押金支付描述
|
||||||
|
YAJIN_PAY_NOTIFY_URL = "http://你的域名/api/shangpin/yajinhuitiao/" # 押金支付回调地址
|
||||||
|
|
||||||
|
# 积分充值相关配置
|
||||||
|
JIFEN_PAY_DESCRIPTION = "打手积分补充" # 积分支付描述
|
||||||
|
JIFEN_PAY_NOTIFY_URL = "http://你的域名/api/shangpin/jifenhuitiao/" # 积分支付回调地址
|
||||||
|
|
||||||
|
# 会员充值相关配置
|
||||||
|
HUIYUAN_PAY_DESCRIPTION = "打手会员充值" # 会员支付描述
|
||||||
|
HUIYUAN_PAY_NOTIFY_URL = "http://你的域名/api/shangpin/huiyuanhuitiao/" # 会员支付回调地址
|
||||||
|
|
||||||
|
|
||||||
|
# 考核支付配置
|
||||||
|
KAOHE_PAY_DESCRIPTION = '考核金牌-缴纳考核费'
|
||||||
|
KAOHE_PAY_NOTIFY_URL = 'https://您的域名/hqhd/yonghu/kh_huitiao' # 替换为实际回调地址
|
||||||
|
KAOHE_PAY_DEFAULT_FEE = 5.00 # 未配置费用时的默认金额
|
||||||
|
|
||||||
|
|
||||||
|
# settings.py
|
||||||
|
SHANGJIA_CZ_PREFIX = "CzSJ" # 商家充值订单前缀
|
||||||
|
SHANGJIA_CZ_PAY_DESCRIPTION = "阿龙电竞-商家余额充值"
|
||||||
|
SHANGJIA_CZ_PAY_NOTIFY_URL = "https://yourdomain.com/shangpin/sjhuitiao"
|
||||||
|
SHANGJIA_CZ_MIN_AMOUNT = 1
|
||||||
|
SHANGJIA_CZ_MAX_AMOUNT = 10000
|
||||||
|
|
||||||
|
# 订单ID前缀配置
|
||||||
|
ORDER_PREFIX = {
|
||||||
|
'yajin': 'CzYJ', # 押金订单前缀
|
||||||
|
'jifen': 'CzJF', # 积分订单前缀
|
||||||
|
'huiyuan': 'CzHY', # 会员订单前缀
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 腾讯云即时通讯 (IM) - 开发环境可暂不配置
|
||||||
|
TENCENT_IM_CONFIG = {
|
||||||
|
'SDK_APP_ID': 0, # 整数
|
||||||
|
'SECRET_KEY': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# 阿里云短信 - 开发环境可暂不配置
|
||||||
|
ALIYUN_SMS_CONFIG = {
|
||||||
|
'ACCESS_KEY_ID': '',
|
||||||
|
'ACCESS_KEY_SECRET': '',
|
||||||
|
'SIGN_NAME': '',
|
||||||
|
'TEMPLATE_CODE': '',
|
||||||
|
'REGION_ID': 'cn-hangzhou',
|
||||||
|
}
|
||||||
|
|
||||||
|
# HashID (用于加密ID,旧项目有,新项目需要吗?)
|
||||||
|
# HASHID_FIELD_SALT = 'a_long_dianjing_2025_salt' # 【待确认】请设置你的盐值
|
||||||
|
|
||||||
|
|
||||||
|
# settings.py 尾部添加
|
||||||
|
# settings.py
|
||||||
|
# settings.py
|
||||||
|
# settings.py 中添加配置
|
||||||
|
GOEASY_APPKEY = '' # 你的AppKey
|
||||||
|
GOEASY_SECRET = '' # 从GoEasy控制台获取
|
||||||
|
GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message' # 正式环境
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# settings.py
|
||||||
|
# H5静态页面域名配置
|
||||||
|
H5_DOMAIN = 'https://h5.yourdomain.com' # 生产环境
|
||||||
|
# H5_DOMAIN = 'https://test-h5.yourdomain.com' # 测试环境
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 特殊管事ID列表(yonghuid),这些管事的首次分红金额从 UserGuanshi.fenhong_yici 字段获取
|
||||||
|
SPECIAL_GUANSHI_IDS = [
|
||||||
|
'1000001',
|
||||||
|
'1000002',
|
||||||
|
# ... 按需添加
|
||||||
|
]
|
||||||
|
|
||||||
|
# ========== 管理员批量链接白名单 ==========
|
||||||
|
ADMIN_BATCH_LINK_ALLOWED_PHONES = [
|
||||||
|
'13800138000',
|
||||||
|
'13912345678',
|
||||||
|
]
|
||||||
|
|
||||||
|
BATCH_LINK_DEFAULT_COUNT = 100
|
||||||
|
BATCH_LINK_MAX_COUNT = 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# settings.py
|
||||||
|
|
||||||
|
# settings.py
|
||||||
|
WECHAT_PAY_V3_CONFIG = {
|
||||||
|
'MCHID': '', # 商户号
|
||||||
|
'APPID': '', # 小程序APPID
|
||||||
|
'API_V3_KEY': '', # APIv3密钥(32字节)
|
||||||
|
'PRIVATE_KEY_PATH': '/opt/1panel/apps/openresty/openresty/www/sites/119.28.221.247.1199/huizhifuzhengshu/apiclient_key.pem', # 商户私钥文件路径
|
||||||
|
'CERT_SERIAL_NO': '', # 商户证书序列号
|
||||||
|
'PLATFORM_CERT_DIR': '/opt/1panel/apps/openresty/openresty/www/sites/119.28.221.247.1199/huizhifuzhengshu/pub_key.pem', # 存放微信平台证书公钥的目录(文件名:序列号.pem)
|
||||||
|
'NOTIFY_URL': 'https://ios808.top/hqhd/yonghu/tixiancallback', # 回调地址
|
||||||
|
'TRANSFER_SCENE_ID': '', # 替换为你的实际场景ID
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CROSS_PLATFORM_SYNC_TOKEN = 'xK9mP2vL8qW5rT7y'
|
||||||
|
|
||||||
|
# 提现收款链路日志(输出到 stderr,gunicorn/1panel 可抓取)
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
|
'handlers': {
|
||||||
|
'tixian_shenhe_console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'verbose',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'formatters': {
|
||||||
|
'verbose': {
|
||||||
|
'format': '[{asctime}] {levelname} {name} {message}',
|
||||||
|
'style': '{',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'yonghu.tixian_shenhe': {
|
||||||
|
'handlers': ['tixian_shenhe_console'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
60
a_long_dianjing/urls.py
Normal file
60
a_long_dianjing/urls.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for a_long_dianjing project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
'''from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
]'''
|
||||||
|
|
||||||
|
# a_long_dianjing/urls.py
|
||||||
|
"""
|
||||||
|
项目根路由配置 - 按照生产环境格式,连接所有APP路由
|
||||||
|
注意:本项目使用MinIO模拟OSS,文件上传不经过Django MEDIA_URL,所以不需要static(settings.MEDIA_URL)配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# Django管理后台
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
# 用户相关路由
|
||||||
|
path('yonghu/', include('yonghu.urls')),
|
||||||
|
|
||||||
|
# 订单相关路由
|
||||||
|
path('dingdan/', include('dingdan.urls')),
|
||||||
|
|
||||||
|
# 商品/陪玩服务相关路由
|
||||||
|
path('shangpin/', include('shangpin.urls')),
|
||||||
|
|
||||||
|
|
||||||
|
# 配置相关路由(系统配置)
|
||||||
|
path('peizhi/', include('peizhi.urls')),
|
||||||
|
|
||||||
|
path('houtai/', include('houtai.urls')),
|
||||||
|
path('shangdian/', include('shangdian.urls')),
|
||||||
|
path('dengji/', include('dengji.urls')),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
# 重要说明:
|
||||||
|
# 1. 本项目使用MinIO(模拟OSS)作为对象存储,文件上传通过MinIO的S3接口直接处理
|
||||||
|
# 2. 文件存储不经过Django的MEDIA_URL,所以不需要配置 static(settings.MEDIA_URL, ...)
|
||||||
|
# 3. 未来迁移到腾讯云COS/阿里云OSS时,也通过django-storages直接处理,不经过Django静态文件服务
|
||||||
|
# 4. 所有文件访问将通过MinIO/COS的CDN域名直接访问,不需要Django路由转发
|
||||||
16
a_long_dianjing/wsgi.py
Normal file
16
a_long_dianjing/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for a_long_dianjing project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
325
app.js
Normal file
325
app.js
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// app.js - 优化生产版本(修复重连机制和用户ID问题)
|
||||||
|
import GoEasy from './static/lib/goeasy-2.13.24.esm.min';
|
||||||
|
const ChatCore = require('./utils/chat-core');
|
||||||
|
import { check } from './utils/phone-auth';
|
||||||
|
|
||||||
|
|
||||||
|
// ===== 角色 → 默认首页(与 custom-tab-bar 完全一致) =====
|
||||||
|
const roleDefaultPage = {
|
||||||
|
normal: '/pages/index/index',
|
||||||
|
dashou: '/pages/dashouduan/dashouduan',
|
||||||
|
shangjia: '/pages/shangjiaduan/shangjiaduan', // 商家首页也是商城
|
||||||
|
guanshi: '/pages/guanshiduan/guanshiduan',
|
||||||
|
zuzhang: '/pages/zuzhangduan/zuzhangduan',
|
||||||
|
kaoheguan: '/pages/kaoheguan/kaoheguan'
|
||||||
|
};
|
||||||
|
|
||||||
|
App({
|
||||||
|
globalData: {
|
||||||
|
|
||||||
|
hasShownPopupOnColdStart: false,
|
||||||
|
|
||||||
|
apiBaseUrl: 'https://6ccy.top/hqhd',
|
||||||
|
ossImageUrl: '',
|
||||||
|
morentouxiang: '',
|
||||||
|
dashouguize: '',
|
||||||
|
|
||||||
|
shangpinliebiao: [],
|
||||||
|
shangpinzhuanqu: [],
|
||||||
|
shangpinleixing: [],
|
||||||
|
shangpinlunbo: [],
|
||||||
|
shangpingonggao: '',
|
||||||
|
lunbozhanwei: '/images/lunbozhanwei.jpg',
|
||||||
|
|
||||||
|
dingdanTiaoshu: {
|
||||||
|
daifuwu: 0,
|
||||||
|
fuwuzhong: 0,
|
||||||
|
yiwancheng: 0,
|
||||||
|
yituikuan: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
dashouqun: '',
|
||||||
|
dashouqunid: '',
|
||||||
|
guanshiqun: '',
|
||||||
|
guanshiqunid: '',
|
||||||
|
|
||||||
|
appId:'wx2517043ca3f39677',
|
||||||
|
|
||||||
|
shangjiastatus: 0,
|
||||||
|
dashoustatus: 0,
|
||||||
|
guanshistatus: 0,
|
||||||
|
dashouNicheng: '',
|
||||||
|
zhanghaoStatus: null,
|
||||||
|
dashouzhuangtai: null,
|
||||||
|
yongjin: null,
|
||||||
|
zonge: null,
|
||||||
|
yajin: null,
|
||||||
|
chenghao: '',
|
||||||
|
jinfen: null,
|
||||||
|
clumber: [{
|
||||||
|
huiyuanid: '',
|
||||||
|
huiyuanming: '',
|
||||||
|
daoqi: ''
|
||||||
|
}],
|
||||||
|
chengjiaoliang: null,
|
||||||
|
zaixianZhuangtai: null,
|
||||||
|
|
||||||
|
guanshi: {
|
||||||
|
nicheng: '',
|
||||||
|
uid: '',
|
||||||
|
touxiang: '',
|
||||||
|
gszhstatus: '',
|
||||||
|
yaoqingzongshu: 0,
|
||||||
|
fenyongzonge: '0.00',
|
||||||
|
fenyongtixian: '0.00'
|
||||||
|
},
|
||||||
|
|
||||||
|
shangjia: {
|
||||||
|
sjzhzhuangtai: '',
|
||||||
|
sjyue: '',
|
||||||
|
fadanzong: null,
|
||||||
|
tuikuanzong: null,
|
||||||
|
riliushui: '',
|
||||||
|
yueliushui: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
liaotian_liebiao: [],
|
||||||
|
xshenfen: 1,
|
||||||
|
|
||||||
|
goEasyConfig: null,
|
||||||
|
kefuConfig: {
|
||||||
|
link: '',
|
||||||
|
enterpriseId: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
cosConfig: {
|
||||||
|
bucket: '',
|
||||||
|
region: 'ap-shanghai',
|
||||||
|
uploadPathPrefix: 'order/'
|
||||||
|
},
|
||||||
|
|
||||||
|
goEasyConnection: {
|
||||||
|
status: 'disconnected',
|
||||||
|
userId: '',
|
||||||
|
identityType: '',
|
||||||
|
lastConnectTime: 0,
|
||||||
|
autoReconnect: true,
|
||||||
|
reconnectAttempts: 0,
|
||||||
|
maxReconnectAttempts: 5,
|
||||||
|
heartbeatInterval: null,
|
||||||
|
cacheKeys: {
|
||||||
|
savedConnection: 'savedGoEasyConnection',
|
||||||
|
userId: 'goEasyUserId',
|
||||||
|
identityType: 'currentGoEasyIdentity',
|
||||||
|
connectTime: 'goEasyConnectTime'
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
heartbeatInterval: 20000,
|
||||||
|
reconnectDelay: 3000,
|
||||||
|
offlineTimeout: 30000,
|
||||||
|
cacheValidityHours: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
messageManager: {
|
||||||
|
unreadTotal: 0,
|
||||||
|
unreadMap: {},
|
||||||
|
latestMessages: [],
|
||||||
|
notificationQueue: [],
|
||||||
|
notificationVisible: false,
|
||||||
|
lastNotificationTime: 0,
|
||||||
|
notificationCooldown: 3000,
|
||||||
|
tabBarIndex: 2,
|
||||||
|
showTabBarBadge: true,
|
||||||
|
customTabBar: true,
|
||||||
|
tabBarBadgeText: 0,
|
||||||
|
soundEnabled: true,
|
||||||
|
vibrationEnabled: true,
|
||||||
|
doNotDisturb: false,
|
||||||
|
doNotDisturbStart: '22:00',
|
||||||
|
doNotDisturbEnd: '08:00',
|
||||||
|
notificationStyle: {
|
||||||
|
position: 'top',
|
||||||
|
duration: 3000,
|
||||||
|
backgroundColor: '#00f7ff',
|
||||||
|
textColor: '#ffffff',
|
||||||
|
borderRadius: '16rpx',
|
||||||
|
boxShadow: '0 10rpx 30rpx rgba(0, 247, 255, 0.3)'
|
||||||
|
},
|
||||||
|
currentNotification: null,
|
||||||
|
cacheKeys: {
|
||||||
|
messageSettings: 'messageSettings',
|
||||||
|
unreadTotal: 'messageUnreadTotal',
|
||||||
|
latestMessages: 'latestMessages',
|
||||||
|
notificationMuted: 'notificationMuted'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pageState: {
|
||||||
|
currentPage: '',
|
||||||
|
isInChatPage: false,
|
||||||
|
currentChatId: '',
|
||||||
|
lastPageUpdate: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
eventListeners: {},
|
||||||
|
debugMode: false,
|
||||||
|
currentUser: null,
|
||||||
|
currentRole: 'normal' // 当前角色,默认老板
|
||||||
|
},
|
||||||
|
|
||||||
|
// ========== 仅修改 onLaunch,其余未动 ==========
|
||||||
|
async onLaunch() {
|
||||||
|
// ---------- ① 立即隐藏“返回首页”按钮 ----------
|
||||||
|
wx.hideHomeButton();
|
||||||
|
wx.onAppRoute(() => {
|
||||||
|
wx.hideHomeButton();
|
||||||
|
});
|
||||||
|
// 🆕 手机号认证检查
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- ② 恢复角色并跳转(不依赖网络) ----------
|
||||||
|
const savedRole = wx.getStorageSync('currentRole') || 'normal';
|
||||||
|
this.globalData.currentRole = savedRole;
|
||||||
|
|
||||||
|
const targetPage = roleDefaultPage[savedRole] || '/pages/index/index';
|
||||||
|
if (savedRole !== 'normal' || targetPage !== '/pages/index/index') {
|
||||||
|
setTimeout(() => {
|
||||||
|
wx.reLaunch({ url: targetPage });
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- ③ 后台静默获取配置,成功后再初始化 GoEasy/IM ----------
|
||||||
|
this.fetchDynamicConfig()
|
||||||
|
.then(config => {
|
||||||
|
this.applyDynamicConfig(config);
|
||||||
|
this.initGoEasyWithConfig();
|
||||||
|
ChatCore.initGlobalMessageSystem(this);
|
||||||
|
this.initCurrentUser();
|
||||||
|
|
||||||
|
const saved = this.getSavedConnection();
|
||||||
|
console.log('【app.js启动】缓存身份:', saved ? saved.identityType : '无',
|
||||||
|
'userId:', saved ? saved.userId : '无');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.ensureConnection();
|
||||||
|
}, 300);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('获取动态配置失败:', error);
|
||||||
|
|
||||||
|
// 兜底初始化,防止后续调用报错
|
||||||
|
this.initGoEasyWithConfig();
|
||||||
|
ChatCore.initGlobalMessageSystem(this);
|
||||||
|
this.initCurrentUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ---------- ④ 所有初始化完成后,最后进行手机号认证检查 ----------
|
||||||
|
try {
|
||||||
|
const needAuth = await check(); // 你已经顶部 import 了
|
||||||
|
if (needAuth) {
|
||||||
|
wx.reLaunch({ url: '/pages/phone-auth/phone-auth' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 以下三个方法完全保持原样
|
||||||
|
fetchDynamicConfig() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.request({
|
||||||
|
url: this.globalData.apiBaseUrl + '/peizhi/qdpzhq',
|
||||||
|
method: 'POST',
|
||||||
|
header: { 'content-type': 'application/json' },
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||||
|
resolve(res.data.data);
|
||||||
|
} else {
|
||||||
|
reject(new Error(res.data?.msg || '接口返回错误'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: reject
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
applyDynamicConfig(config) {
|
||||||
|
if (config.cos) {
|
||||||
|
this.globalData.ossImageUrl = config.cos.ossImageUrl || '';
|
||||||
|
this.globalData.cosConfig = {
|
||||||
|
bucket: config.cos.bucket || '',
|
||||||
|
region: config.cos.region || 'ap-shanghai',
|
||||||
|
uploadPathPrefix: config.cos.uploadPathPrefix || 'order/'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (config.goEasy) {
|
||||||
|
this.globalData.goEasyConfig = {
|
||||||
|
host: config.goEasy.host || 'hangzhou.goeasy.io',
|
||||||
|
appkey: config.goEasy.appkey || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (config.otherConfig) {
|
||||||
|
this.globalData.morentouxiang = config.otherConfig.morentouxiang || '';
|
||||||
|
this.globalData.dashouguize = config.otherConfig.dashouguize || '';
|
||||||
|
}
|
||||||
|
if (config.kefu) {
|
||||||
|
this.globalData.kefuConfig = {
|
||||||
|
link: config.kefu.link || '',
|
||||||
|
enterpriseId: config.kefu.enterpriseId || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initGoEasyWithConfig() {
|
||||||
|
const cfg = this.globalData.goEasyConfig;
|
||||||
|
if (!cfg || !cfg.appkey) {
|
||||||
|
console.error('GoEasy appkey 未配置,无法初始化');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wx.goEasy = GoEasy.getInstance({
|
||||||
|
host: cfg.host || 'hangzhou.goeasy.io',
|
||||||
|
appkey: cfg.appkey,
|
||||||
|
modules: ['im', 'pubsub']
|
||||||
|
});
|
||||||
|
wx.GoEasy = GoEasy;
|
||||||
|
},
|
||||||
|
|
||||||
|
initCurrentUser() {
|
||||||
|
const uid = wx.getStorageSync('uid');
|
||||||
|
if (uid) {
|
||||||
|
this.globalData.currentUser = {
|
||||||
|
id: uid,
|
||||||
|
name: '用户' + uid.substring(0, 6),
|
||||||
|
avatar: this.globalData.ossImageUrl + this.globalData.morentouxiang
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emitEvent(eventName, data) {
|
||||||
|
if (this.globalData.eventListeners[eventName]) {
|
||||||
|
this.globalData.eventListeners[eventName].forEach(callback => {
|
||||||
|
try { callback(data); } catch (error) { console.error(error); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
on(eventName, callback) {
|
||||||
|
if (!this.globalData.eventListeners[eventName]) {
|
||||||
|
this.globalData.eventListeners[eventName] = [];
|
||||||
|
}
|
||||||
|
this.globalData.eventListeners[eventName].push(callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
off(eventName, callback) {
|
||||||
|
if (this.globalData.eventListeners[eventName]) {
|
||||||
|
const index = this.globalData.eventListeners[eventName].indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this.globalData.eventListeners[eventName].splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
107
app.json
Normal file
107
app.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
"pages/index/index",
|
||||||
|
"pages/fenlei/fenlei",
|
||||||
|
"pages/xuanren/xuanren",
|
||||||
|
"pages/xiaoxi/xiaoxi",
|
||||||
|
"pages/wode/wode",
|
||||||
|
"pages/shangpinxiangqing/shangpinxiangqing",
|
||||||
|
"pages/tijiao/tijiao",
|
||||||
|
"pages/xiugai/xiugai",
|
||||||
|
"pages/dashouduan/dashouduan",
|
||||||
|
"pages/shangjiaduan/shangjiaduan",
|
||||||
|
"pages/guanshiduan/guanshiduan",
|
||||||
|
"pages/dingdan/dingdan",
|
||||||
|
"pages/dingdanxiangqing/dingdanxiangqing",
|
||||||
|
"pages/dashouguize/dashouguize",
|
||||||
|
"pages/dashouxiugai/dashouxiugai",
|
||||||
|
"pages/dashoupaihang/dashoupaihang",
|
||||||
|
"pages/dashouchongzhi/dashouchongzhi",
|
||||||
|
"pages/dashouxiaoxi/dashouxiaoxi",
|
||||||
|
"pages/dashoudingdan/dashoudingdan",
|
||||||
|
"pages/jiedan/jiedan",
|
||||||
|
"pages/tixian/tixian",
|
||||||
|
"pages/yaoqingdashou/yaoqingdashou",
|
||||||
|
"pages/wodedashou/wodedashou",
|
||||||
|
"pages/czjilu/czjilu",
|
||||||
|
"pages/guanshipaihang/guanshipaihang",
|
||||||
|
"pages/sjpaidan/sjpaidan",
|
||||||
|
"pages/sjdingdan/sjdingdan",
|
||||||
|
"pages/sjxiaoxi/sjxiaoxi",
|
||||||
|
"pages/sjpaihang/sjpaihang",
|
||||||
|
"pages/sjchongzhi/sjchongzhi",
|
||||||
|
"pages/sjddxq/sjddxq",
|
||||||
|
"pages/dsddxq/dsddxq",
|
||||||
|
"pages/liaotian/liaotian",
|
||||||
|
"pages/jisufd/jisufd",
|
||||||
|
"pages/jiedanchi/jiedanchi",
|
||||||
|
"pages/haibao/haibao",
|
||||||
|
"pages/jinpaids/jinpaids",
|
||||||
|
"pages/zuzhangduan/zuzhangduan",
|
||||||
|
"pages/guanzhual/guanzhual",
|
||||||
|
"pages/zzfhjilu/zzfhjilu",
|
||||||
|
"pages/yqguanshi/yqguanshi",
|
||||||
|
"components/global-notification/global-notification",
|
||||||
|
"components/popup-notice/popup-notice",
|
||||||
|
"pages/tixian/components/mode1/mode1",
|
||||||
|
"pages/tixian/components/mode2/mode2",
|
||||||
|
|
||||||
|
"pages/jiedanchi2/jiedanchi2",
|
||||||
|
"pages/qunliaotian/qunliaotian",
|
||||||
|
"pages/kefuliaotian/kefuliaotian",
|
||||||
|
"pages/renzheng/renzheng",
|
||||||
|
|
||||||
|
"components/order-card/order-card",
|
||||||
|
"components/order-sender/order-sender",
|
||||||
|
"pages/cfss/cfss/cfss",
|
||||||
|
"pages/cfss/components/fakuan-list/fakuan-list",
|
||||||
|
"pages/cfss/components/jifen-list/jifen-list",
|
||||||
|
"pages/cfss/components/fakuan-pay/fakuan-pay",
|
||||||
|
"pages/phone-auth/phone-auth",
|
||||||
|
"components/chenghao-tag/chenghao-tag",
|
||||||
|
"pages/kaoheguan/kaoheguan",
|
||||||
|
"pages/kaohe_dafen/kaohe_dafen",
|
||||||
|
"pages/kaohe_jilu/kaohe_jilu",
|
||||||
|
"pages/kaohe_zhongxin/kaohe_zhongxin",
|
||||||
|
"pages/kaohe_jinpai/kaohe_jinpai"
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
"usingComponents": {
|
||||||
|
"popup-notice": "/components/popup-notice/popup-notice",
|
||||||
|
"custom-tab-bar": "/custom-tab-bar/index"
|
||||||
|
},
|
||||||
|
|
||||||
|
"window": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationStyle": "default",
|
||||||
|
"backgroundTextStyle": "light",
|
||||||
|
"navigationBarBackgroundColor": "#fff",
|
||||||
|
"navigationBarTitleText": "二H电竞"
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"custom": true,
|
||||||
|
"list": [
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"pagePath": "pages/jiedanchi/jiedanchi",
|
||||||
|
"text": "接单池",
|
||||||
|
"iconPath": "/images/jiedanchi.png",
|
||||||
|
"selectedIconPath": "/images/jiedanchi.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/jiedanchi2/jiedanchi2",
|
||||||
|
"text": "接单池",
|
||||||
|
"iconPath": "/images/jiedanchi.png",
|
||||||
|
"selectedIconPath": "/images/jiedanchi.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"style": "v2",
|
||||||
|
"sitemapLocation": "sitemap.json",
|
||||||
|
"lazyCodeLoading": "requiredComponents"
|
||||||
|
}
|
||||||
10
app.wxss
Normal file
10
app.wxss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**app.wxss**/
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 200rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
73
components/chenghao-tag/chenghao-tag.js
Normal file
73
components/chenghao-tag/chenghao-tag.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// components/chenghao-tag/chenghao-tag.js
|
||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
mingcheng: { type: String, value: '' },
|
||||||
|
texiaoJson: { type: Object, value: {} }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
// 默认值:宽152,高52,六边形,无背景图,无动画,白色字
|
||||||
|
width: 152,
|
||||||
|
height: 52,
|
||||||
|
shapeClass: 'liubianxing', // 保证至少不是矩形
|
||||||
|
animationClass: '',
|
||||||
|
bgStyle: '',
|
||||||
|
textColor: '#FFFFFF',
|
||||||
|
textSize: 22,
|
||||||
|
imageUrl: ''
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
const cfg = this.properties.texiaoJson || {};
|
||||||
|
|
||||||
|
// 1. 尺寸(稍大一些,一行能放3~4个)
|
||||||
|
const width = cfg.width || 152;
|
||||||
|
const height = cfg.height || 52;
|
||||||
|
|
||||||
|
// 2. 背景处理:优先背景图,否则用渐变/纯色
|
||||||
|
let bgStyle = '';
|
||||||
|
let imageUrl = '';
|
||||||
|
if (cfg.image_url) {
|
||||||
|
const ossImageUrl = app.globalData.ossImageUrl || '';
|
||||||
|
imageUrl = cfg.image_url.startsWith('http')
|
||||||
|
? cfg.image_url
|
||||||
|
: ossImageUrl + cfg.image_url;
|
||||||
|
} else if (cfg.bg_gradient) {
|
||||||
|
bgStyle = `background: ${cfg.bg_gradient};`;
|
||||||
|
} else if (cfg.bg_color) {
|
||||||
|
bgStyle = `background: ${cfg.bg_color};`;
|
||||||
|
} else {
|
||||||
|
// 默认金橙渐变
|
||||||
|
bgStyle = `background: linear-gradient(135deg, #FFD700, #FF8C00);`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 形状(后端传入 shape 字段,默认 liubianxing)
|
||||||
|
const shapeClass = cfg.shape || 'liubianxing';
|
||||||
|
|
||||||
|
// 4. 动画
|
||||||
|
const animationClass = cfg.animation || '';
|
||||||
|
|
||||||
|
// 5. 文字
|
||||||
|
const textColor = cfg.text_color || '#FFFFFF';
|
||||||
|
const textSize = cfg.text_size || 22;
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
width, height,
|
||||||
|
bgStyle, imageUrl,
|
||||||
|
shapeClass, animationClass,
|
||||||
|
textColor, textSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 背景图加载失败时回退为纯色背景
|
||||||
|
onImageError() {
|
||||||
|
this.setData({ imageUrl: '' });
|
||||||
|
// 如果 bgStyle 为空,给个默认背景
|
||||||
|
if (!this.data.bgStyle) {
|
||||||
|
this.setData({ bgStyle: 'background: linear-gradient(135deg, #FFD700, #FF8C00);' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
components/chenghao-tag/chenghao-tag.json
Normal file
4
components/chenghao-tag/chenghao-tag.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
20
components/chenghao-tag/chenghao-tag.wxml
Normal file
20
components/chenghao-tag/chenghao-tag.wxml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!-- components/chenghao-tag/chenghao-tag.wxml -->
|
||||||
|
<view
|
||||||
|
class="tag-root {{shapeClass}} {{animationClass}}"
|
||||||
|
style="width: {{width}}rpx; height: {{height}}rpx; {{bgStyle}}"
|
||||||
|
>
|
||||||
|
<!-- 背景图(如果有) -->
|
||||||
|
<image
|
||||||
|
wx:if="{{imageUrl}}"
|
||||||
|
class="tag-bg-image"
|
||||||
|
src="{{imageUrl}}"
|
||||||
|
mode="aspectFill"
|
||||||
|
binderror="onImageError"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- 文字层 -->
|
||||||
|
<text
|
||||||
|
class="tag-text"
|
||||||
|
style="color: {{textColor}}; font-size: {{textSize}}rpx;"
|
||||||
|
>{{mingcheng}}</text>
|
||||||
|
</view>
|
||||||
90
components/chenghao-tag/chenghao-tag.wxss
Normal file
90
components/chenghao-tag/chenghao-tag.wxss
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/* components/chenghao-tag/chenghao-tag.wxss */
|
||||||
|
|
||||||
|
/* ========== 根容器 ========== */
|
||||||
|
.tag-root {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 4rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 背景图 ========== */
|
||||||
|
.tag-bg-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 文字层 ========== */
|
||||||
|
.tag-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 0 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 多边形形状 ========== */
|
||||||
|
.tag-root.liubianxing {
|
||||||
|
clip-path: polygon(12% 0%, 88% 0%, 100% 50%, 88% 100%, 12% 100%, 0% 50%);
|
||||||
|
}
|
||||||
|
.tag-root.lingxing {
|
||||||
|
clip-path: polygon(50% 0%, 85% 12%, 100% 50%, 85% 88%, 50% 100%, 15% 88%, 0% 50%, 15% 12%);
|
||||||
|
}
|
||||||
|
.tag-root.wujiaoxing {
|
||||||
|
clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
|
||||||
|
}
|
||||||
|
.tag-root.dunpai {
|
||||||
|
clip-path: polygon(18% 0%, 82% 0%, 100% 18%, 95% 50%, 100% 82%, 82% 100%, 18% 100%, 0% 82%, 5% 50%, 0% 18%);
|
||||||
|
}
|
||||||
|
.tag-root.jiantou {
|
||||||
|
clip-path: polygon(0% 0%, 85% 0%, 100% 50%, 85% 100%, 0% 100%, 12% 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 动画 ========== */
|
||||||
|
/* 流光扫描 */
|
||||||
|
.tag-root.shine::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent);
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
animation: shine-anim 2s infinite;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@keyframes shine-anim {
|
||||||
|
0% { left: -100%; }
|
||||||
|
100% { left: 200%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 呼吸光晕 */
|
||||||
|
.tag-root.pulse {
|
||||||
|
animation: pulse-anim 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse-anim {
|
||||||
|
0%, 100% { filter: brightness(1); transform: scale(1); }
|
||||||
|
50% { filter: brightness(1.2); transform: scale(1.04); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 外发光闪烁 */
|
||||||
|
.tag-root.glow {
|
||||||
|
animation: glow-anim 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes glow-anim {
|
||||||
|
0%, 100% { box-shadow: 0 0 8rpx rgba(255, 215, 0, 0.4); }
|
||||||
|
50% { box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.8), 0 0 40rpx rgba(255, 215, 0, 0.4); }
|
||||||
|
}
|
||||||
366
components/global-notification/global-notification.js
Normal file
366
components/global-notification/global-notification.js
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
value: 'top'
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
value: 3000
|
||||||
|
},
|
||||||
|
showProgress: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
showMuteButton: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
showTime: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
swipeToClose: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
swipeThreshold: {
|
||||||
|
type: Number,
|
||||||
|
value: 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data: {
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
message: '',
|
||||||
|
avatar: '',
|
||||||
|
notificationData: null,
|
||||||
|
progress: 100,
|
||||||
|
progressInterval: null,
|
||||||
|
positionClass: 'top',
|
||||||
|
|
||||||
|
// 滑动相关
|
||||||
|
touchStartY: 0,
|
||||||
|
touchStartX: 0,
|
||||||
|
currentY: 0,
|
||||||
|
currentX: 0,
|
||||||
|
isSwiping: false,
|
||||||
|
swipingClass: '',
|
||||||
|
swipeStyle: '',
|
||||||
|
swipeDistance: 0,
|
||||||
|
|
||||||
|
// 静音状态
|
||||||
|
isMuted: false,
|
||||||
|
|
||||||
|
// 自动隐藏计时器
|
||||||
|
autoHideTimeout: null
|
||||||
|
},
|
||||||
|
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
this.loadMuteSetting();
|
||||||
|
this.setData({
|
||||||
|
positionClass: this.properties.position
|
||||||
|
});
|
||||||
|
this.setupDirectEventListeners();
|
||||||
|
},
|
||||||
|
|
||||||
|
detached() {
|
||||||
|
this.clearTimers();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setupDirectEventListeners() {
|
||||||
|
const app = getApp();
|
||||||
|
if (app) {
|
||||||
|
const self = this;
|
||||||
|
const globalNotification = {
|
||||||
|
show: (data) => {
|
||||||
|
if (self && self.showNotification) {
|
||||||
|
self.showNotification(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hide: () => {
|
||||||
|
if (self && self.hideNotification) {
|
||||||
|
self.hideNotification();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isMuted: () => {
|
||||||
|
return self.data.isMuted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
app.globalData.globalNotification = globalNotification;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadMuteSetting() {
|
||||||
|
try {
|
||||||
|
const isMuted = wx.getStorageSync('notificationMuted') || false;
|
||||||
|
this.setData({ isMuted });
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载静音设置失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveMuteSetting(isMuted) {
|
||||||
|
try {
|
||||||
|
wx.setStorageSync('notificationMuted', isMuted);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('保存静音设置失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showNotification(data) {
|
||||||
|
if (!data) return;
|
||||||
|
if (this.data.isMuted) return;
|
||||||
|
|
||||||
|
this.clearTimers();
|
||||||
|
|
||||||
|
const app = getApp();
|
||||||
|
let avatar = data.avatar;
|
||||||
|
if (!avatar && app && app.globalData) {
|
||||||
|
avatar = app.globalData.ossImageUrl + app.globalData.morentouxiang;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
show: true,
|
||||||
|
title: data.senderName || '新消息',
|
||||||
|
message: data.content || '[消息内容]',
|
||||||
|
avatar: avatar,
|
||||||
|
notificationData: data,
|
||||||
|
progress: 100,
|
||||||
|
swipingClass: '',
|
||||||
|
swipeStyle: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.properties.showProgress) {
|
||||||
|
this.startProgressBar(this.properties.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.autoHideTimeout = setTimeout(() => {
|
||||||
|
this.hideNotification();
|
||||||
|
}, this.properties.duration);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideNotification() {
|
||||||
|
this.clearTimers();
|
||||||
|
this.setData({
|
||||||
|
show: false,
|
||||||
|
progress: 100,
|
||||||
|
swipingClass: '',
|
||||||
|
swipeStyle: ''
|
||||||
|
});
|
||||||
|
this.triggerEvent('hide');
|
||||||
|
},
|
||||||
|
|
||||||
|
startProgressBar(duration) {
|
||||||
|
const interval = 100;
|
||||||
|
const steps = duration / interval;
|
||||||
|
const stepValue = 100 / steps;
|
||||||
|
|
||||||
|
let currentProgress = 100;
|
||||||
|
this.data.progressInterval = setInterval(() => {
|
||||||
|
currentProgress -= stepValue;
|
||||||
|
if (currentProgress <= 0) {
|
||||||
|
currentProgress = 0;
|
||||||
|
this.setData({ progress: currentProgress });
|
||||||
|
clearInterval(this.data.progressInterval);
|
||||||
|
} else {
|
||||||
|
this.setData({ progress: currentProgress });
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearTimers() {
|
||||||
|
if (this.data.autoHideTimeout) {
|
||||||
|
clearTimeout(this.data.autoHideTimeout);
|
||||||
|
this.data.autoHideTimeout = null;
|
||||||
|
}
|
||||||
|
if (this.data.progressInterval) {
|
||||||
|
clearInterval(this.data.progressInterval);
|
||||||
|
this.data.progressInterval = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchStart(e) {
|
||||||
|
if (!this.properties.swipeToClose) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
this.setData({
|
||||||
|
touchStartY: touch.clientY,
|
||||||
|
touchStartX: touch.clientX,
|
||||||
|
currentY: touch.clientY,
|
||||||
|
currentX: touch.clientX,
|
||||||
|
isSwiping: false
|
||||||
|
});
|
||||||
|
this.clearTimers();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchMove(e) {
|
||||||
|
if (!this.properties.swipeToClose) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const deltaY = touch.clientY - this.data.touchStartY;
|
||||||
|
const deltaX = touch.clientX - this.data.touchStartX;
|
||||||
|
const absDeltaY = Math.abs(deltaY);
|
||||||
|
const absDeltaX = Math.abs(deltaX);
|
||||||
|
|
||||||
|
if (absDeltaY > 10 && absDeltaY > absDeltaX) {
|
||||||
|
this.setData({
|
||||||
|
isSwiping: true,
|
||||||
|
currentY: touch.clientY,
|
||||||
|
swipingClass: deltaY < 0 ? 'swiping-up' : 'swiping',
|
||||||
|
swipeDistance: Math.abs(deltaY),
|
||||||
|
swipeStyle: `--swipe-distance: ${Math.abs(deltaY)}rpx;`
|
||||||
|
});
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onTouchEnd(e) {
|
||||||
|
if (!this.properties.swipeToClose || !this.data.isSwiping) {
|
||||||
|
this.restartAutoHide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deltaY = this.data.currentY - this.data.touchStartY;
|
||||||
|
const absDeltaY = Math.abs(deltaY);
|
||||||
|
if (absDeltaY > this.properties.swipeThreshold) {
|
||||||
|
this.hideNotification();
|
||||||
|
} else {
|
||||||
|
this.setData({
|
||||||
|
swipingClass: '',
|
||||||
|
swipeStyle: 'transition: transform 0.3s ease; transform: translateY(0);'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setData({
|
||||||
|
swipingClass: '',
|
||||||
|
swipeStyle: ''
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
this.restartAutoHide();
|
||||||
|
}
|
||||||
|
this.setData({ isSwiping: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
restartAutoHide() {
|
||||||
|
const remainingTime = (this.data.progress / 100) * this.properties.duration;
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
this.data.autoHideTimeout = setTimeout(() => {
|
||||||
|
this.hideNotification();
|
||||||
|
}, remainingTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onTap() {
|
||||||
|
const { notificationData } = this.data;
|
||||||
|
if (!notificationData) return;
|
||||||
|
this.triggerEvent('tap', notificationData);
|
||||||
|
this.hideNotification();
|
||||||
|
this.navigateToChatPage(notificationData);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.triggerEvent('close');
|
||||||
|
this.hideNotification();
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const newMutedState = !this.data.isMuted;
|
||||||
|
this.setData({ isMuted: newMutedState });
|
||||||
|
this.saveMuteSetting(newMutedState);
|
||||||
|
|
||||||
|
const app = getApp();
|
||||||
|
if (app && app.toggleDoNotDisturb) {
|
||||||
|
app.toggleDoNotDisturb(newMutedState);
|
||||||
|
}
|
||||||
|
this.triggerEvent('mute', { muted: newMutedState });
|
||||||
|
wx.showToast({
|
||||||
|
title: newMutedState ? '已开启免打扰' : '已关闭免打扰',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1500
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newMutedState) {
|
||||||
|
this.hideNotification();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
formatTime(timestamp) {
|
||||||
|
if (!timestamp) return '';
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now - date;
|
||||||
|
if (diff < 60 * 1000) return '刚刚';
|
||||||
|
if (diff < 60 * 60 * 1000) return Math.floor(diff / (60 * 1000)) + '分钟前';
|
||||||
|
if (date.toDateString() === now.toDateString()) {
|
||||||
|
return date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
const yesterday = new Date(now);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
if (date.toDateString() === yesterday.toDateString()) {
|
||||||
|
return '昨天 ' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
return (date.getMonth() + 1) + '月' + date.getDate() + '日';
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToChatPage(data) {
|
||||||
|
if (!data || !data.message) {
|
||||||
|
console.warn('通知数据无效,无法跳转');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = data.message;
|
||||||
|
let path = '';
|
||||||
|
let queryParam = null;
|
||||||
|
|
||||||
|
// 根据 notificationType 或消息字段判断
|
||||||
|
if (data.notificationType === 'cs' || msg.teamId) {
|
||||||
|
// 客服消息
|
||||||
|
const teamId = data.teamId || msg.teamId;
|
||||||
|
path = '/pages/kefuliaotian/kefuliaotian';
|
||||||
|
queryParam = { teamId: teamId };
|
||||||
|
} else if (data.notificationType === 'group' || msg.groupId) {
|
||||||
|
// 群聊消息
|
||||||
|
const groupId = data.groupId || msg.groupId;
|
||||||
|
const orderId = data.orderId || msg.orderId || '';
|
||||||
|
const groupName = data.groupName || '订单群聊';
|
||||||
|
const groupAvatar = data.groupAvatar || '';
|
||||||
|
const isCross = data.isCross || msg.isCross || 0;
|
||||||
|
path = '/pages/qunliaotian/qunliaotian';
|
||||||
|
queryParam = {
|
||||||
|
groupId,
|
||||||
|
orderId,
|
||||||
|
groupName,
|
||||||
|
groupAvatar,
|
||||||
|
isCross
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 默认私聊
|
||||||
|
const toUserId = data.senderId || msg.senderId;
|
||||||
|
const toName = data.senderName || '用户';
|
||||||
|
const toAvatar = data.avatar || '';
|
||||||
|
path = '/pages/liaotian/liaotian';
|
||||||
|
queryParam = {
|
||||||
|
toUserId,
|
||||||
|
toName,
|
||||||
|
toAvatar
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path && queryParam) {
|
||||||
|
const encoded = encodeURIComponent(JSON.stringify(queryParam));
|
||||||
|
wx.navigateTo({
|
||||||
|
url: `${path}?data=${encoded}`,
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('通知跳转失败:', err);
|
||||||
|
wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
wx.switchTab({ url: '/pages/xiaoxi/xiaoxi' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
components/global-notification/global-notification.json
Normal file
4
components/global-notification/global-notification.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
49
components/global-notification/global-notification.wxml
Normal file
49
components/global-notification/global-notification.wxml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<view
|
||||||
|
class="global-notification {{show ? 'show' : ''}} {{positionClass}} {{swipingClass}}"
|
||||||
|
bindtap="onTap"
|
||||||
|
bindtouchstart="onTouchStart"
|
||||||
|
bindtouchmove="onTouchMove"
|
||||||
|
bindtouchend="onTouchEnd"
|
||||||
|
style="{{swipeStyle}}"
|
||||||
|
data-notification-id="{{notificationData && notificationData.id ? notificationData.id : ''}}">
|
||||||
|
|
||||||
|
<!-- 滑动关闭指示器 -->
|
||||||
|
<view class="notification-swipe-indicator {{isSwiping ? 'active' : ''}}">
|
||||||
|
<view class="swipe-line"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 通知内容 -->
|
||||||
|
<view class="notification-content">
|
||||||
|
<!-- 左侧头像 -->
|
||||||
|
<image class="notification-avatar" src="{{avatar}}" mode="aspectFill"></image>
|
||||||
|
|
||||||
|
<!-- 中间内容区域 -->
|
||||||
|
<view class="notification-body">
|
||||||
|
<view class="notification-title">{{title}}</view>
|
||||||
|
<view class="notification-message">{{message}}</view>
|
||||||
|
<view class="notification-time" wx:if="{{showTime}}">
|
||||||
|
{{formatTime(notificationData ? notificationData.timestamp : '')}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧操作区域 -->
|
||||||
|
<view class="notification-actions">
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<view class="notification-close" catchtap="onClose">
|
||||||
|
<text class="close-icon">×</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 免打扰按钮 -->
|
||||||
|
<view class="notification-mute {{isMuted ? 'muted' : ''}}" catchtap="onMute" wx:if="{{showMuteButton}}">
|
||||||
|
<text class="mute-icon">
|
||||||
|
{{isMuted ? '🔕' : '🔔'}}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<view class="notification-progress" wx:if="{{showProgress}}">
|
||||||
|
<view class="progress-bar {{progress < 30 ? 'warning' : ''}}"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
215
components/global-notification/global-notification.wxss
Normal file
215
components/global-notification/global-notification.wxss
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
.global-notification {
|
||||||
|
position: fixed;
|
||||||
|
left: 20rpx;
|
||||||
|
right: 20rpx;
|
||||||
|
top: 0rpx;
|
||||||
|
z-index: 99999;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-120%);
|
||||||
|
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
rgba(41, 47, 61, 0.98) 0%,
|
||||||
|
rgba(31, 36, 48, 0.98) 100%);
|
||||||
|
backdrop-filter: blur(30rpx) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(30rpx) saturate(180%);
|
||||||
|
border: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||||
|
box-shadow:
|
||||||
|
0 20rpx 60rpx rgba(0, 0, 0, 0.4),
|
||||||
|
0 0 0 1rpx rgba(255, 255, 255, 0.03),
|
||||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.1);
|
||||||
|
min-height: 140rpx;
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-notification.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-swipe-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swipe-line {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 4rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 2rpx;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-swipe-indicator.active .swipe-line {
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
transform: scaleX(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
min-height: 140rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-avatar {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 3rpx solid rgba(212, 175, 55, 0.6);
|
||||||
|
box-shadow:
|
||||||
|
0 8rpx 24rpx rgba(212, 175, 55, 0.2),
|
||||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.3);
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: linear-gradient(135deg, #D4AF37 0%, #FFD700 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
color: rgba(255, 255, 255, 0.95);
|
||||||
|
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-top: 4rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close,
|
||||||
|
.notification-mute {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||||
|
transition: all 0.3s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow:
|
||||||
|
0 4rpx 12rpx rgba(0, 0, 0, 0.2),
|
||||||
|
inset 0 1rpx 0 rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close:active,
|
||||||
|
.notification-mute:active {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-icon {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 0;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-mute.muted .mute-icon {
|
||||||
|
color: #D4AF37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-progress {
|
||||||
|
height: 6rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0 0 24rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
#D4AF37 0%,
|
||||||
|
#FFD700 50%,
|
||||||
|
#D4AF37 100%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
transition: width 0.1s linear;
|
||||||
|
box-shadow: 0 0 20rpx rgba(212, 175, 55, 0.4);
|
||||||
|
animation: progressShine 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progressShine {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar.warning {
|
||||||
|
background: linear-gradient(90deg,
|
||||||
|
#ff3b30 0%,
|
||||||
|
#ff6b6b 50%,
|
||||||
|
#ff3b30 100%);
|
||||||
|
animation: progressShine 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-notification.swiping {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: translateY(calc(var(--swipe-distance, 0) * -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-notification.swiping-up {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: translateY(calc(var(--swipe-distance, 0) * -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-notification:active:not(.swiping) {
|
||||||
|
transform: scale(0.995) translateY(0);
|
||||||
|
transition: transform 0.1s;
|
||||||
|
}
|
||||||
102
components/navigation-bar/navigation-bar.js
Normal file
102
components/navigation-bar/navigation-bar.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
Component({
|
||||||
|
options: {
|
||||||
|
multipleSlots: true // 在组件定义时的选项中启用多slot支持
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
properties: {
|
||||||
|
extClass: {
|
||||||
|
type: String,
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
back: {
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false
|
||||||
|
},
|
||||||
|
homeButton: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
animated: {
|
||||||
|
// 显示隐藏的时候opacity动画效果
|
||||||
|
type: Boolean,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
// 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在
|
||||||
|
type: Boolean,
|
||||||
|
value: true,
|
||||||
|
observer: '_showChange'
|
||||||
|
},
|
||||||
|
// back为true的时候,返回的页面深度
|
||||||
|
delta: {
|
||||||
|
type: Number,
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的初始数据
|
||||||
|
*/
|
||||||
|
data: {
|
||||||
|
displayStyle: ''
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
const rect = wx.getMenuButtonBoundingClientRect()
|
||||||
|
const platform = (wx.getDeviceInfo() || wx.getSystemInfoSync()).platform
|
||||||
|
const isAndroid = platform === 'android'
|
||||||
|
const isDevtools = platform === 'devtools'
|
||||||
|
const { windowWidth, safeArea: { top = 0, bottom = 0 } = {} } = wx.getWindowInfo() || wx.getSystemInfoSync()
|
||||||
|
this.setData({
|
||||||
|
ios: !isAndroid,
|
||||||
|
innerPaddingRight: `padding-right: ${windowWidth - rect.left}px`,
|
||||||
|
leftWidth: `width: ${windowWidth - rect.left}px`,
|
||||||
|
safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${top}px); padding-top: ${top}px` : ``
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {
|
||||||
|
_showChange(show) {
|
||||||
|
const animated = this.data.animated
|
||||||
|
let displayStyle = ''
|
||||||
|
if (animated) {
|
||||||
|
displayStyle = `opacity: ${show ? '1' : '0'
|
||||||
|
};transition:opacity 0.5s;`
|
||||||
|
} else {
|
||||||
|
displayStyle = `display: ${show ? '' : 'none'}`
|
||||||
|
}
|
||||||
|
this.setData({
|
||||||
|
displayStyle
|
||||||
|
})
|
||||||
|
},
|
||||||
|
back() {
|
||||||
|
const data = this.data
|
||||||
|
if (data.delta) {
|
||||||
|
wx.navigateBack({
|
||||||
|
delta: data.delta
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.triggerEvent('back', { delta: data.delta }, {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
5
components/navigation-bar/navigation-bar.json
Normal file
5
components/navigation-bar/navigation-bar.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"styleIsolation": "apply-shared",
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
64
components/navigation-bar/navigation-bar.wxml
Normal file
64
components/navigation-bar/navigation-bar.wxml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<view class="weui-navigation-bar {{extClass}}">
|
||||||
|
<view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}}">
|
||||||
|
|
||||||
|
<!-- 左侧按钮 -->
|
||||||
|
<view class='weui-navigation-bar__left' style="{{leftWidth}}">
|
||||||
|
<block wx:if="{{back || homeButton}}">
|
||||||
|
<!-- 返回上一页 -->
|
||||||
|
<block wx:if="{{back}}">
|
||||||
|
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback">
|
||||||
|
<view
|
||||||
|
bindtap="back"
|
||||||
|
class="weui-navigation-bar__btn_goback_wrapper"
|
||||||
|
hover-class="weui-active"
|
||||||
|
hover-stay-time="100"
|
||||||
|
aria-role="button"
|
||||||
|
aria-label="返回"
|
||||||
|
>
|
||||||
|
<view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
<!-- 返回首页 -->
|
||||||
|
<block wx:if="{{homeButton}}">
|
||||||
|
<view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home">
|
||||||
|
<view
|
||||||
|
bindtap="home"
|
||||||
|
class="weui-navigation-bar__btn_home_wrapper"
|
||||||
|
hover-class="weui-active"
|
||||||
|
aria-role="button"
|
||||||
|
aria-label="首页"
|
||||||
|
>
|
||||||
|
<view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</block>
|
||||||
|
<block wx:else>
|
||||||
|
<slot name="left"></slot>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class='weui-navigation-bar__center'>
|
||||||
|
<view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert">
|
||||||
|
<view
|
||||||
|
class="weui-loading"
|
||||||
|
aria-role="img"
|
||||||
|
aria-label="加载中"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
<block wx:if="{{title}}">
|
||||||
|
<text>{{title}}</text>
|
||||||
|
</block>
|
||||||
|
<block wx:else>
|
||||||
|
<slot name="center"></slot>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧留空 -->
|
||||||
|
<view class='weui-navigation-bar__right'>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
96
components/navigation-bar/navigation-bar.wxss
Normal file
96
components/navigation-bar/navigation-bar.wxss
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
.weui-navigation-bar {
|
||||||
|
--weui-FG-0:rgba(0,0,0,.9);
|
||||||
|
--height: 44px;
|
||||||
|
--left: 16px;
|
||||||
|
}
|
||||||
|
.weui-navigation-bar .android {
|
||||||
|
--height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar {
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--weui-FG-0);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__inner {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: calc(var(--height) + env(safe-area-inset-top));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__left {
|
||||||
|
position: relative;
|
||||||
|
padding-left: var(--left);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__btn_goback_wrapper {
|
||||||
|
padding: 11px 18px 11px 16px;
|
||||||
|
margin: -11px -18px -11px -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__btn_goback_wrapper.weui-active {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__btn_goback {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 12px;
|
||||||
|
height: 24px;
|
||||||
|
-webkit-mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
|
||||||
|
mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
|
||||||
|
-webkit-mask-size: cover;
|
||||||
|
mask-size: cover;
|
||||||
|
background-color: var(--weui-FG-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__center {
|
||||||
|
font-size: 17px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-navigation-bar__loading {
|
||||||
|
margin-right: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weui-loading {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: block;
|
||||||
|
background: transparent url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' viewBox='0 0 80 80' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Eloading%3C/title%3E%3Cdefs%3E%3ClinearGradient x1='94.0869141%25' y1='0%25' x2='94.0869141%25' y2='90.559082%25' id='linearGradient-1'%3E%3Cstop stop-color='%23606060' stop-opacity='0' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3ClinearGradient x1='100%25' y1='8.67370605%25' x2='100%25' y2='90.6286621%25' id='linearGradient-2'%3E%3Cstop stop-color='%23606060' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' opacity='0.9'%3E%3Cg%3E%3Cpath d='M40,0 C62.09139,0 80,17.90861 80,40 C80,62.09139 62.09139,80 40,80 L40,73 C58.2253967,73 73,58.2253967 73,40 C73,21.7746033 58.2253967,7 40,7 L40,0 Z' fill='url(%23linearGradient-1)'%3E%3C/path%3E%3Cpath d='M40,0 L40,7 C21.7746033,7 7,21.7746033 7,40 C7,58.2253967 21.7746033,73 40,73 L40,80 C17.90861,80 0,62.09139 0,40 C0,17.90861 17.90861,0 40,0 Z' fill='url(%23linearGradient-2)'%3E%3C/path%3E%3Ccircle id='Oval' fill='%23606060' cx='40.5' cy='3.5' r='3.5'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A") no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
animation: loading linear infinite 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
from {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
102
components/order-card/order-card.js
Normal file
102
components/order-card/order-card.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
orderId: { type: String, value: '' }
|
||||||
|
},
|
||||||
|
|
||||||
|
data: {
|
||||||
|
order: null,
|
||||||
|
loading: false,
|
||||||
|
errorMsg: '',
|
||||||
|
// 跨平台字段
|
||||||
|
isCross: false, // 是否跨平台订单
|
||||||
|
dispatchType: 0, // 1-我方派单,2-对方派单
|
||||||
|
partnerClubId: '', // 对方俱乐部ID
|
||||||
|
},
|
||||||
|
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
if (this.properties.orderId) {
|
||||||
|
this.fetchOrderDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// 获取订单详情(含跨平台字段)
|
||||||
|
fetchOrderDetail() {
|
||||||
|
if (!this.properties.orderId) return;
|
||||||
|
this.setData({ loading: true, errorMsg: '' });
|
||||||
|
const baseUrl = app.globalData.apiBaseUrl;
|
||||||
|
wx.request({
|
||||||
|
url: `${baseUrl}/dingdan/order_detail/`, // 需后端返回 is_cross, dispatch_type, partner_club_id
|
||||||
|
method: 'POST',
|
||||||
|
data: { order_id: this.properties.orderId },
|
||||||
|
header: { 'content-type': 'application/json' },
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||||
|
const order = res.data.data;
|
||||||
|
// 处理图片URL
|
||||||
|
if (order.tupian && !order.tupian.startsWith('http')) {
|
||||||
|
order.tupian = app.globalData.ossImageUrl + order.tupian;
|
||||||
|
}
|
||||||
|
this.setData({
|
||||||
|
order,
|
||||||
|
loading: false,
|
||||||
|
isCross: order.is_cross === 1,
|
||||||
|
dispatchType: order.dispatch_type || 0,
|
||||||
|
partnerClubId: order.partner_club_id || '',
|
||||||
|
});
|
||||||
|
// 通知父页面状态
|
||||||
|
this.triggerEvent('statusready', {
|
||||||
|
isCross: this.data.isCross,
|
||||||
|
dispatchType: this.data.dispatchType,
|
||||||
|
partnerClubId: this.data.partnerClubId
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setData({ errorMsg: res.data?.msg || '获取订单失败', loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.setData({ errorMsg: '网络请求失败', loading: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送订单卡片消息(原有)
|
||||||
|
sendOrderMessage() {
|
||||||
|
if (!this.data.order) return;
|
||||||
|
this.triggerEvent('sendorder', { order: this.data.order });
|
||||||
|
},
|
||||||
|
|
||||||
|
// 跨平台消息转发(供父页面调用)
|
||||||
|
forwardMessage(payload) {
|
||||||
|
const that = this;
|
||||||
|
const baseUrl = app.globalData.apiBaseUrl;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.request({
|
||||||
|
url: `${baseUrl}/dingdan/kptxxts`,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
order_id: that.properties.orderId,
|
||||||
|
message_type: payload.type || 'text',
|
||||||
|
content: payload.content || '',
|
||||||
|
image_url: payload.imageUrl || '', // 图片等
|
||||||
|
},
|
||||||
|
header: { 'content-type': 'application/json' },
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200 && res.data && res.data.code === 0) {
|
||||||
|
resolve(res.data);
|
||||||
|
} else {
|
||||||
|
reject(res.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
components/order-card/order-card.json
Normal file
4
components/order-card/order-card.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
20
components/order-card/order-card.wxml
Normal file
20
components/order-card/order-card.wxml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<view class="order-card" wx:if="{{order}}">
|
||||||
|
<view class="card-header">
|
||||||
|
<text class="card-title">关联订单</text>
|
||||||
|
<text class="card-id">{{order.dingdan_id}}</text>
|
||||||
|
<!-- 跨平台标记 -->
|
||||||
|
<text class="cross-tag" wx:if="{{isCross}}">跨平台</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-body" bindtap="sendOrderMessage">
|
||||||
|
<image class="card-img" src="{{order.tupian || '/images/default-goods.png'}}" mode="aspectFill" />
|
||||||
|
<view class="card-info">
|
||||||
|
<text class="card-jieshao">{{order.jieshao || '暂无介绍'}}</text>
|
||||||
|
<text class="card-jine">¥{{order.jine}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-send-btn">发送</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{errorMsg}}" class="card-error">{{errorMsg}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="order-card" wx:elif="{{loading}}">
|
||||||
|
<text class="loading-text">加载订单信息...</text>
|
||||||
|
</view>
|
||||||
39
components/order-card/order-card.wxss
Normal file
39
components/order-card/order-card.wxss
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.order-card {
|
||||||
|
background: #fff;
|
||||||
|
margin: 15rpx 20rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.card-title { font-size: 26rpx; color: #666; }
|
||||||
|
.card-id { font-size: 24rpx; color: #999; margin-left: 15rpx; }
|
||||||
|
.cross-tag {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #ff9900;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
padding: 2rpx 12rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
.card-body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
.card-img { width: 100rpx; height: 100rpx; border-radius: 8rpx; margin-right: 15rpx; }
|
||||||
|
.card-info { flex: 1; }
|
||||||
|
.card-jieshao { font-size: 28rpx; color: #333; }
|
||||||
|
.card-jine { font-size: 26rpx; color: #e74c3c; }
|
||||||
|
.card-send-btn {
|
||||||
|
width: 80rpx; height: 50rpx; line-height: 50rpx;
|
||||||
|
text-align: center; background: #07c160; color: #fff;
|
||||||
|
border-radius: 8rpx; font-size: 24rpx;
|
||||||
|
}
|
||||||
|
.card-error { padding: 20rpx; color: #e74c3c; text-align: center; }
|
||||||
|
.loading-text { display: block; padding: 20rpx; text-align: center; color: #999; }
|
||||||
16
components/order-sender/order-sender.js
Normal file
16
components/order-sender/order-sender.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
visible: Boolean,
|
||||||
|
showDetail: { type: Boolean, value: false }
|
||||||
|
},
|
||||||
|
|
||||||
|
data: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.triggerEvent('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
components/order-sender/order-sender.json
Normal file
4
components/order-sender/order-sender.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
12
components/order-sender/order-sender.wxml
Normal file
12
components/order-sender/order-sender.wxml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<view class="order-sender-mask" wx:if="{{visible}}" catchtap="close"></view>
|
||||||
|
<view class="order-sender {{visible ? 'show' : ''}}">
|
||||||
|
<view class="header">
|
||||||
|
<text class="title">提示</text>
|
||||||
|
<text class="close" bindtap="close">✕</text>
|
||||||
|
</view>
|
||||||
|
<view class="content">
|
||||||
|
<view class="icon">📋</view>
|
||||||
|
<text class="notice">此功能暂未开放</text>
|
||||||
|
<text class="sub">敬请期待</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
45
components/order-sender/order-sender.wxss
Normal file
45
components/order-sender/order-sender.wxss
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.order-sender-mask {
|
||||||
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
.order-sender {
|
||||||
|
position: fixed; bottom: 0; left: 0; right: 0;
|
||||||
|
height: 360rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
z-index: 2001;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: 0.25s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.order-sender.show { transform: translateY(0); }
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex; justify-content: space-between;
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.title { font-size: 32rpx; font-weight: 600; }
|
||||||
|
.close { font-size: 44rpx; color: #999; padding: 0 12rpx; }
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
.icon { font-size: 80rpx; margin-bottom: 20rpx; }
|
||||||
|
.notice {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
.sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
324
components/popup-notice/popup-notice.js
Normal file
324
components/popup-notice/popup-notice.js
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
// components/popup-notice/popup-notice.js
|
||||||
|
import PopupService from '../../services/popupService.js';
|
||||||
|
|
||||||
|
Component({
|
||||||
|
data: {
|
||||||
|
// 原有全屏弹窗数据
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
images: [],
|
||||||
|
processedImages: [],
|
||||||
|
hasImages: false,
|
||||||
|
hasTextOnly: false,
|
||||||
|
textContent: '',
|
||||||
|
duration: 0,
|
||||||
|
remainingSeconds: 0,
|
||||||
|
muteChecked: false,
|
||||||
|
showMuteCheckbox: false,
|
||||||
|
isFullscreen: false,
|
||||||
|
timer: null,
|
||||||
|
|
||||||
|
// 悬浮窗相关数据
|
||||||
|
isMinimized: false,
|
||||||
|
floatX: 20,
|
||||||
|
floatY: 200,
|
||||||
|
savedRemainingSeconds: 0,
|
||||||
|
floatAvatarUrl: '',
|
||||||
|
|
||||||
|
// 🆕 自定义预览数据
|
||||||
|
showCustomPreview: false, // 是否显示自定义预览遮罩
|
||||||
|
previewUrls: [], // 当前预览的图片URL列表
|
||||||
|
currentPreviewIndex: 0, // 当前预览图片索引
|
||||||
|
},
|
||||||
|
|
||||||
|
lifetimes: {
|
||||||
|
detached() {
|
||||||
|
this.clearTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// ========== 原有所有方法(完全不变,一字不漏) ==========
|
||||||
|
async show(config, onCloseCallback) {
|
||||||
|
const { title, images, duration, showMuteCheckbox, serverTime } = config;
|
||||||
|
this.onCloseCallback = onCloseCallback;
|
||||||
|
|
||||||
|
let safeImages = JSON.parse(JSON.stringify(images || []));
|
||||||
|
const hasImages = safeImages.length > 0;
|
||||||
|
let hasTextOnly = false, textContent = '';
|
||||||
|
if (!hasImages) {
|
||||||
|
hasTextOnly = true;
|
||||||
|
textContent = config.text || '暂无内容';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
visible: true,
|
||||||
|
isMinimized: false,
|
||||||
|
title: title || '',
|
||||||
|
images: safeImages,
|
||||||
|
hasImages,
|
||||||
|
hasTextOnly,
|
||||||
|
textContent,
|
||||||
|
duration: duration || 0,
|
||||||
|
remainingSeconds: duration || 0,
|
||||||
|
muteChecked: false,
|
||||||
|
showMuteCheckbox: showMuteCheckbox || false,
|
||||||
|
isFullscreen: false,
|
||||||
|
processedImages: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (duration > 0) {
|
||||||
|
this.startCountdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasImages) {
|
||||||
|
const processed = [];
|
||||||
|
const screenWidth = wx.getSystemInfoSync().windowWidth;
|
||||||
|
const containerWidth = screenWidth * 0.9;
|
||||||
|
const imageWidth = containerWidth - 60;
|
||||||
|
for (let i = 0; i < safeImages.length; i++) {
|
||||||
|
const img = safeImages[i];
|
||||||
|
const fullUrl = this.getFullImageUrl(img.url);
|
||||||
|
let imageHeight = 200;
|
||||||
|
try {
|
||||||
|
const res = await this.getImageInfoPromise(fullUrl);
|
||||||
|
const realHeight = (imageWidth * res.height) / res.width;
|
||||||
|
imageHeight = realHeight;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取图片信息失败', fullUrl, e);
|
||||||
|
}
|
||||||
|
processed.push({
|
||||||
|
url: fullUrl,
|
||||||
|
text: img.text,
|
||||||
|
height: imageHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setData({ processedImages: processed });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getImageInfoPromise(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.getImageInfo({
|
||||||
|
src: url,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
hide(muteToday = false) {
|
||||||
|
this.clearTimer();
|
||||||
|
this.setData({
|
||||||
|
visible: false,
|
||||||
|
isMinimized: false
|
||||||
|
});
|
||||||
|
if (this.onCloseCallback) {
|
||||||
|
this.onCloseCallback({ muteToday });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startCountdown() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
let sec = this.data.remainingSeconds;
|
||||||
|
if (sec <= 1) {
|
||||||
|
this.clearTimer();
|
||||||
|
this.setData({ remainingSeconds: 0 });
|
||||||
|
} else {
|
||||||
|
this.setData({ remainingSeconds: sec - 1 });
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearTimer() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseTap() {
|
||||||
|
if (this.data.remainingSeconds > 0) {
|
||||||
|
wx.showToast({
|
||||||
|
title: `请阅读${this.data.remainingSeconds}秒后再关闭`,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.data.showMuteCheckbox && this.data.muteChecked) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '今天将不再收到本页面的任何公告,确认吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) this.hide(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.hide(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMuteChange(e) {
|
||||||
|
this.setData({ muteChecked: e.detail.value.length > 0 });
|
||||||
|
},
|
||||||
|
|
||||||
|
// 🆕 替换原有的 onPreviewImage,改用自定义预览(不触发 onShow)
|
||||||
|
onCustomPreview(e) {
|
||||||
|
const index = e.currentTarget.dataset.index;
|
||||||
|
const urls = this.data.processedImages.map(img => img.url);
|
||||||
|
this.setData({
|
||||||
|
showCustomPreview: true,
|
||||||
|
previewUrls: urls,
|
||||||
|
currentPreviewIndex: index !== undefined ? index : 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 🆕 关闭自定义预览
|
||||||
|
onCloseCustomPreview() {
|
||||||
|
this.setData({
|
||||||
|
showCustomPreview: false,
|
||||||
|
previewUrls: [],
|
||||||
|
currentPreviewIndex: 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 🆕 swiper 切换事件
|
||||||
|
onPreviewSwiperChange(e) {
|
||||||
|
this.setData({
|
||||||
|
currentPreviewIndex: e.detail.current
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 保留原 onPreviewImage 方法以防外部调用,但不再使用
|
||||||
|
onPreviewImage(e) {
|
||||||
|
// 重定向到自定义预览
|
||||||
|
this.onCustomPreview(e);
|
||||||
|
},
|
||||||
|
|
||||||
|
onImageError(e) {
|
||||||
|
console.error('图片加载失败:', e.currentTarget.dataset.url);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFullscreen() {
|
||||||
|
this.setData({ isFullscreen: !this.data.isFullscreen });
|
||||||
|
},
|
||||||
|
|
||||||
|
getFullImageUrl(relativeUrl) {
|
||||||
|
if (!relativeUrl) return '';
|
||||||
|
if (relativeUrl.startsWith('http')) return relativeUrl;
|
||||||
|
const app = getApp();
|
||||||
|
let base = app.globalData.ossImageUrl || '';
|
||||||
|
base = base.replace(/\/+$/, '');
|
||||||
|
const clean = relativeUrl.replace(/^\/+/, '');
|
||||||
|
return base ? base + '/' + clean : clean;
|
||||||
|
},
|
||||||
|
|
||||||
|
preventMove() {},
|
||||||
|
|
||||||
|
onMinimize() {
|
||||||
|
const remaining = this.data.remainingSeconds;
|
||||||
|
this.clearTimer();
|
||||||
|
|
||||||
|
const app = getApp();
|
||||||
|
let avatarUrl = '';
|
||||||
|
if (app.globalData.morentouxiang) {
|
||||||
|
avatarUrl = app.globalData.morentouxiang;
|
||||||
|
} else {
|
||||||
|
avatarUrl = '/images/default-avatar.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avatarUrl && !avatarUrl.startsWith('http')) {
|
||||||
|
const ossBase = app.globalData.ossImageUrl || '';
|
||||||
|
avatarUrl = ossBase.replace(/\/+$/, '') + '/' + avatarUrl.replace(/^\/+/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
visible: false,
|
||||||
|
isMinimized: true,
|
||||||
|
savedRemainingSeconds: remaining,
|
||||||
|
floatX: 20,
|
||||||
|
floatY: 200,
|
||||||
|
floatAvatarUrl: avatarUrl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatChange(e) {
|
||||||
|
this.setData({
|
||||||
|
floatX: e.detail.x,
|
||||||
|
floatY: e.detail.y
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatTap() {
|
||||||
|
this.setData({
|
||||||
|
visible: true,
|
||||||
|
isMinimized: false,
|
||||||
|
remainingSeconds: this.data.savedRemainingSeconds
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.data.duration > 0 && this.data.savedRemainingSeconds > 0) {
|
||||||
|
this.startCountdown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatAvatarError() {
|
||||||
|
this.setData({
|
||||||
|
floatAvatarUrl: '/images/avatar-placeholder.png'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.setData({
|
||||||
|
visible: false,
|
||||||
|
isMinimized: false
|
||||||
|
});
|
||||||
|
PopupService.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatTouchStart(e) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
this.touchStartX = touch.clientX;
|
||||||
|
this.touchStartY = touch.clientY;
|
||||||
|
this.startFloatX = this.data.floatX;
|
||||||
|
this.startFloatY = this.data.floatY;
|
||||||
|
this.hasMoved = false;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatTouchMove(e) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const deltaX = touch.clientX - this.touchStartX;
|
||||||
|
const deltaY = touch.clientY - this.touchStartY;
|
||||||
|
|
||||||
|
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||||
|
this.hasMoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newX = this.startFloatX + deltaX;
|
||||||
|
let newY = this.startFloatY + deltaY;
|
||||||
|
|
||||||
|
const systemInfo = wx.getSystemInfoSync();
|
||||||
|
const screenWidth = systemInfo.windowWidth;
|
||||||
|
const screenHeight = systemInfo.windowHeight;
|
||||||
|
const estimatedSize = 60;
|
||||||
|
|
||||||
|
newX = Math.max(0, Math.min(newX, screenWidth - estimatedSize));
|
||||||
|
newY = Math.max(0, Math.min(newY, screenHeight - estimatedSize));
|
||||||
|
this.setData({ floatX: newX, floatY: newY });
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onFloatTouchEnd(e) {
|
||||||
|
if (!this.hasMoved) {
|
||||||
|
this.onFloatTap();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 🆕 阻止事件冒泡(用于预览遮罩内部点击不关闭)
|
||||||
|
stopPropagation() {}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
components/popup-notice/popup-notice.json
Normal file
4
components/popup-notice/popup-notice.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
110
components/popup-notice/popup-notice.wxml
Normal file
110
components/popup-notice/popup-notice.wxml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<!-- components/popup-notice/popup-notice.wxml -->
|
||||||
|
|
||||||
|
<!-- 全屏弹窗遮罩层(完全不变) -->
|
||||||
|
<view class="popup-mask" wx:if="{{visible}}" catchtouchmove="preventMove">
|
||||||
|
<view class="popup-container {{isFullscreen ? 'fullscreen' : ''}}">
|
||||||
|
<!-- 右上角操作按钮组 -->
|
||||||
|
<view class="action-buttons">
|
||||||
|
<!-- 最小化按钮 -->
|
||||||
|
<view class="minimize-btn" bindtap="onMinimize">
|
||||||
|
<text class="btn-icon">−</text>
|
||||||
|
</view>
|
||||||
|
<!-- 全屏切换按钮 -->
|
||||||
|
<view class="fullscreen-btn" bindtap="toggleFullscreen">
|
||||||
|
<text class="btn-icon">{{isFullscreen ? '✕' : '⛶'}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view wx:if="{{title}}" class="popup-title">{{title}}</view>
|
||||||
|
|
||||||
|
<!-- 可滚动内容区 -->
|
||||||
|
<view class="scroll-wrapper">
|
||||||
|
<scroll-view scroll-y class="scroll-view" enhanced show-scrollbar="{{true}}">
|
||||||
|
<view wx:if="{{hasTextOnly}}" class="text-content">{{textContent}}</view>
|
||||||
|
<view wx:else>
|
||||||
|
<view wx:for="{{processedImages}}" wx:key="index" class="image-block">
|
||||||
|
<view wx:if="{{item.text}}" class="block-text">{{item.text}}</view>
|
||||||
|
<view class="image-padding">
|
||||||
|
<image
|
||||||
|
class="block-image"
|
||||||
|
src="{{item.url}}"
|
||||||
|
style="width:100%; height:{{item.height}}px; display:block;"
|
||||||
|
data-index="{{index}}"
|
||||||
|
data-url="{{item.url}}"
|
||||||
|
bindtap="onCustomPreview"
|
||||||
|
binderror="onImageError"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部固定区 -->
|
||||||
|
<view class="bottom-fixed">
|
||||||
|
<view wx:if="{{remainingSeconds > 0}}" class="countdown-tip">
|
||||||
|
请阅读 {{remainingSeconds}} 秒后关闭
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{remainingSeconds === 0 && showMuteCheckbox}}" class="mute-row">
|
||||||
|
<checkbox-group bindchange="onMuteChange">
|
||||||
|
<checkbox value="mute" color="#000" />
|
||||||
|
<text class="mute-label">今日不再提示</text>
|
||||||
|
</checkbox-group>
|
||||||
|
</view>
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<view
|
||||||
|
class="close-btn {{remainingSeconds > 0 ? 'close-btn-disabled' : ''}}"
|
||||||
|
bindtap="{{remainingSeconds > 0 ? '' : 'onCloseTap'}}"
|
||||||
|
>
|
||||||
|
<text class="close-btn-text">{{remainingSeconds > 0 ? '阅读中...' : '关闭'}}</text>
|
||||||
|
<view class="close-btn-sweep" wx:if="{{remainingSeconds === 0}}"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 悬浮窗(自定义触摸拖动) -->
|
||||||
|
<view
|
||||||
|
class="floating-avatar-wrapper"
|
||||||
|
wx:if="{{isMinimized}}"
|
||||||
|
style="left: {{floatX}}px; top: {{floatY}}px;"
|
||||||
|
catchtouchstart="onFloatTouchStart"
|
||||||
|
catchtouchmove="onFloatTouchMove"
|
||||||
|
catchtouchend="onFloatTouchEnd"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="floating-avatar"
|
||||||
|
src="{{floatAvatarUrl}}"
|
||||||
|
mode="aspectFill"
|
||||||
|
binderror="onFloatAvatarError"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 🆕 自定义图片预览遮罩(全屏,不触发页面生命周期) -->
|
||||||
|
<view
|
||||||
|
class="custom-preview-mask"
|
||||||
|
wx:if="{{showCustomPreview}}"
|
||||||
|
catchtouchmove="preventMove"
|
||||||
|
bindtap="onCloseCustomPreview"
|
||||||
|
>
|
||||||
|
<view class="preview-header">
|
||||||
|
<text class="preview-index">{{currentPreviewIndex + 1}} / {{previewUrls.length}}</text>
|
||||||
|
<view class="preview-close" bindtap="onCloseCustomPreview">×</view>
|
||||||
|
</view>
|
||||||
|
<swiper
|
||||||
|
class="preview-swiper"
|
||||||
|
current="{{currentPreviewIndex}}"
|
||||||
|
bindchange="onPreviewSwiperChange"
|
||||||
|
indicator-dots="{{false}}"
|
||||||
|
>
|
||||||
|
<swiper-item wx:for="{{previewUrls}}" wx:key="index">
|
||||||
|
<image
|
||||||
|
src="{{item}}"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="preview-image"
|
||||||
|
catchtap="stopPropagation"
|
||||||
|
/>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
295
components/popup-notice/popup-notice.wxss
Normal file
295
components/popup-notice/popup-notice.wxss
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/* components/popup-notice/popup-notice.wxss */
|
||||||
|
|
||||||
|
/* ========== 全屏弹窗样式(原有,完全不变) ========== */
|
||||||
|
.popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-container {
|
||||||
|
width: 90%;
|
||||||
|
height: 85vh;
|
||||||
|
background: white;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-container.fullscreen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
position: absolute;
|
||||||
|
top: 20rpx;
|
||||||
|
right: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimize-btn,
|
||||||
|
.fullscreen-btn {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(4rpx);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimize-btn:active,
|
||||||
|
.fullscreen-btn:active {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
padding: 30rpx 30rpx 20rpx;
|
||||||
|
border-bottom: 2rpx solid #eee;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-view {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 30rpx;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
padding: 16rpx 30rpx 4rpx 30rpx;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-padding {
|
||||||
|
padding: 0 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-image {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-fixed {
|
||||||
|
background: white;
|
||||||
|
border-top: 1rpx solid #eee;
|
||||||
|
padding: 20rpx 0 30rpx 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.countdown-tip {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #ff6600;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #555;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🔥 关闭按钮(替换原有 button,增加扫光) */
|
||||||
|
.close-btn {
|
||||||
|
width: 600rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-radius: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn-disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn-sweep {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
animation: sweep 2.5s infinite ease-in-out;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sweep {
|
||||||
|
0% { left: -100%; }
|
||||||
|
40% { left: 100%; }
|
||||||
|
100% { left: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 悬浮窗样式(纯圆形,适配自定义触摸拖动) ========== */
|
||||||
|
.floating-avatar-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
z-index: 10001;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: left, top;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
|
||||||
|
border: 2rpx solid rgba(255, 215, 0, 0.6);
|
||||||
|
background: #f0f0f0;
|
||||||
|
display: block;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 以下原有 movable-view 相关样式已弃用,但保留以维持代码完整(用户要求不删代码) */
|
||||||
|
.floating-area {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.floating-view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.float-avatar-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.float-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 自定义图片预览遮罩样式(全屏,不触发 onShow) ========== */
|
||||||
|
.custom-preview-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.95);
|
||||||
|
z-index: 100000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
color: white;
|
||||||
|
font-size: 32rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-index {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-close {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 48rpx;
|
||||||
|
color: white;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-swiper {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
144
config/roles.js
Normal file
144
config/roles.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 角色配置表 —— 用于动态切换底部 TabBar 展示内容
|
||||||
|
* 每个角色的 tabList 数组元素顺序决定 TabBar 显示顺序
|
||||||
|
* 字段说明:
|
||||||
|
* pagePath : 页面路径(以 pages/ 开头)
|
||||||
|
* text : TabBar 文字
|
||||||
|
* iconPath : 未选中图标(相对路径)
|
||||||
|
* selectedIconPath: 选中图标
|
||||||
|
*/
|
||||||
|
const roles = {
|
||||||
|
// 普通老板(默认)
|
||||||
|
normal: {
|
||||||
|
name: '普通老板',
|
||||||
|
defaultPage: 'pages/index/index',
|
||||||
|
tabList: [
|
||||||
|
{
|
||||||
|
pagePath: 'pages/index/index',
|
||||||
|
text: '商城',
|
||||||
|
iconPath: '/images/zhuye.png',
|
||||||
|
selectedIconPath: '/images/zhuye.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/fenlei/fenlei',
|
||||||
|
text: '分类',
|
||||||
|
iconPath: '/images/fenlei.png',
|
||||||
|
selectedIconPath: '/images/fenlei.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/xiaoxi/xiaoxi',
|
||||||
|
text: '消息',
|
||||||
|
iconPath: '/images/xiaoxi.png',
|
||||||
|
selectedIconPath: '/images/xiaoxi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/wode/wode',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/images/wode.png',
|
||||||
|
selectedIconPath: '/images/wode.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 打手
|
||||||
|
dashou: {
|
||||||
|
name: '打手',
|
||||||
|
defaultPage: 'pages/jiedanchi/jiedanchi',
|
||||||
|
tabList: [
|
||||||
|
{
|
||||||
|
pagePath: 'pages/jiedanchi/jiedanchi',
|
||||||
|
text: '接单池',
|
||||||
|
iconPath: '/images/jiedanchi.png',
|
||||||
|
selectedIconPath: '/images/jiedanchi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/xiaoxi/xiaoxi',
|
||||||
|
text: '消息',
|
||||||
|
iconPath: '/images/xiaoxi.png',
|
||||||
|
selectedIconPath: '/images/xiaoxi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/dashoudingdan/dashoudingdan',
|
||||||
|
text: '订单',
|
||||||
|
iconPath: '/images/dashoudingdan_icon.png', // 请替换为实际图标
|
||||||
|
selectedIconPath: '/images/dashoudingdan_icon.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/dashouduan/dashouduan',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/images/dashouduan_icon.png',
|
||||||
|
selectedIconPath: '/images/dashouduan_icon.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 商家
|
||||||
|
shangjia: {
|
||||||
|
name: '商家',
|
||||||
|
defaultPage: 'pages/index/index',
|
||||||
|
tabList: [
|
||||||
|
{
|
||||||
|
pagePath: 'pages/index/index',
|
||||||
|
text: '首页',
|
||||||
|
iconPath: '/images/zhuye.png',
|
||||||
|
selectedIconPath: '/images/zhuye.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/sjdingdan/sjdingdan',
|
||||||
|
text: '订单',
|
||||||
|
iconPath: '/images/sjdingdan_icon.png', // 请替换为实际图标
|
||||||
|
selectedIconPath: '/images/sjdingdan_icon.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/xiaoxi/xiaoxi',
|
||||||
|
text: '消息',
|
||||||
|
iconPath: '/images/xiaoxi.png',
|
||||||
|
selectedIconPath: '/images/xiaoxi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/shangjiaduan/shangjiaduan',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/images/shangjiaduan_icon.png',
|
||||||
|
selectedIconPath: '/images/shangjiaduan_icon.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 管事
|
||||||
|
guanshi: {
|
||||||
|
name: '管事',
|
||||||
|
defaultPage: 'pages/guanshiduan/guanshiduan',
|
||||||
|
tabList: [
|
||||||
|
{
|
||||||
|
pagePath: 'pages/xiaoxi/xiaoxi',
|
||||||
|
text: '消息',
|
||||||
|
iconPath: '/images/xiaoxi.png',
|
||||||
|
selectedIconPath: '/images/xiaoxi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/guanshiduan/guanshiduan',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/images/guanshiduan_icon.png',
|
||||||
|
selectedIconPath: '/images/guanshiduan_icon.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 组长
|
||||||
|
zuzhang: {
|
||||||
|
name: '组长',
|
||||||
|
defaultPage: 'pages/zuzhangduan/zuzhangduan',
|
||||||
|
tabList: [
|
||||||
|
{
|
||||||
|
pagePath: 'pages/xiaoxi/xiaoxi',
|
||||||
|
text: '消息',
|
||||||
|
iconPath: '/images/xiaoxi.png',
|
||||||
|
selectedIconPath: '/images/xiaoxi.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: 'pages/zuzhangduan/zuzhangduan',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/images/zuzhangduan_icon.png',
|
||||||
|
selectedIconPath: '/images/zuzhangduan_icon.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { roles };
|
||||||
194
custom-tab-bar/index.js
Normal file
194
custom-tab-bar/index.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// custom-tab-bar/index.js
|
||||||
|
const app = getApp();
|
||||||
|
|
||||||
|
const roleCfg = {
|
||||||
|
normal: {
|
||||||
|
name: '点单老板',
|
||||||
|
defaultPage: '/pages/index/index',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/index/index', text: '商城', iconPath: '/images/zhuye.png', selectedIconPath: '/images/zhuye.png' },
|
||||||
|
{ pagePath: 'pages/fenlei/fenlei', text: '分类', iconPath: '/images/fenlei.png', selectedIconPath: '/images/fenlei.png' },
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/wode/wode', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dashou: {
|
||||||
|
name: '打手',
|
||||||
|
defaultPage: '/pages/jiedan/jiedan',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/jiedan/jiedan', text: '接单池', iconPath: '/images/jiedanchi.png', selectedIconPath: '/images/jiedanchi.png' },
|
||||||
|
{ pagePath: 'pages/dashoudingdan/dashoudingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/dashouduan/dashouduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
shangjia: {
|
||||||
|
name: '商家',
|
||||||
|
defaultPage: 'pages/sjdingdan/sjdingdan',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/sjdingdan/sjdingdan', text: '订单', iconPath: '/images/dingdan.png', selectedIconPath: '/images/dingdan.png' },
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/shangjiaduan/shangjiaduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
guanshi: {
|
||||||
|
name: '管事',
|
||||||
|
defaultPage: '/pages/guanshiduan/guanshiduan',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/guanshiduan/guanshiduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
zuzhang: {
|
||||||
|
name: '组长',
|
||||||
|
defaultPage: '/pages/zuzhangduan/zuzhangduan',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/zuzhangduan/zuzhangduan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
kaoheguan: {
|
||||||
|
name: '考核官',
|
||||||
|
defaultPage: '/pages/kaoheguan/kaoheguan',
|
||||||
|
tabList: [
|
||||||
|
{ pagePath: 'pages/xiaoxi/xiaoxi', text: '消息', iconPath: '/images/xiaoxi.png', selectedIconPath: '/images/xiaoxi.png' },
|
||||||
|
{ pagePath: 'pages/kaoheguan/kaoheguan', text: '我的', iconPath: '/images/wode.png', selectedIconPath: '/images/wode.png' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Component({
|
||||||
|
data: {
|
||||||
|
selectedIndex: 0,
|
||||||
|
tabList: [],
|
||||||
|
currentRole: 'normal',
|
||||||
|
showRolePicker: false,
|
||||||
|
availableRoles: [],
|
||||||
|
badgeText: ''
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
this.refresh();
|
||||||
|
this._badgeListener = (data) => {
|
||||||
|
if (data.badgeText !== undefined) {
|
||||||
|
this.setData({ badgeText: data.badgeText });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
app.on('tabBarBadgeChanged', this._badgeListener);
|
||||||
|
},
|
||||||
|
detached() {
|
||||||
|
if (this._badgeListener) {
|
||||||
|
app.off('tabBarBadgeChanged', this._badgeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageLifetimes: {
|
||||||
|
show() {
|
||||||
|
this.refresh();
|
||||||
|
const globalBadge = app.globalData.messageManager?.tabBarBadgeText || '';
|
||||||
|
if (this.data.badgeText !== globalBadge) {
|
||||||
|
this.setData({ badgeText: globalBadge });
|
||||||
|
}
|
||||||
|
this._syncUnreadWithDebounce();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_syncUnreadWithDebounce() {
|
||||||
|
if (this._syncTimer) clearTimeout(this._syncTimer);
|
||||||
|
this._syncTimer = setTimeout(() => {
|
||||||
|
if (app && app.loadConversations) {
|
||||||
|
app.loadConversations();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
const role = app.globalData.currentRole || 'normal';
|
||||||
|
let cfg = roleCfg[role];
|
||||||
|
if (!cfg) return;
|
||||||
|
|
||||||
|
// 打手身份下,如果 isJinpai=1,悄悄把“我的”页面换成金牌页面
|
||||||
|
if (role === 'dashou') {
|
||||||
|
const isJinpai = wx.getStorageSync('isJinpai') === 1;
|
||||||
|
if (isJinpai) {
|
||||||
|
cfg = {
|
||||||
|
...cfg,
|
||||||
|
tabList: cfg.tabList.map(item => {
|
||||||
|
if (item.pagePath === 'pages/dashouduan/dashouduan') {
|
||||||
|
return { ...item, pagePath: 'pages/jinpaids/jinpaids' };
|
||||||
|
}
|
||||||
|
return { ...item };
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = cfg.tabList.map((v, i) => ({ ...v, index: i }));
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const curRoute = pages.length ? pages[pages.length - 1].route : '';
|
||||||
|
let idx = list.findIndex(t => t.pagePath === curRoute);
|
||||||
|
if (idx === -1) idx = 0;
|
||||||
|
const avail = this._getAvail();
|
||||||
|
this.setData({ currentRole: role, tabList: list, selectedIndex: idx, availableRoles: avail });
|
||||||
|
},
|
||||||
|
|
||||||
|
_getAvail() {
|
||||||
|
const arr = [{ key: 'normal', name: '点单老板' }];
|
||||||
|
if (wx.getStorageSync('dashoustatus') === 1) arr.push({ key: 'dashou', name: '打手' });
|
||||||
|
if (wx.getStorageSync('shangjiastatus') === 1) arr.push({ key: 'shangjia', name: '商家' });
|
||||||
|
if (wx.getStorageSync('guanshistatus') === 1) arr.push({ key: 'guanshi', name: '管事' });
|
||||||
|
if (wx.getStorageSync('zuzhangstatus') === 1) arr.push({ key: 'zuzhang', name: '组长' });
|
||||||
|
if (wx.getStorageSync('kaoheguanstatus') === 1) arr.push({ key: 'kaoheguan', name: '考核官' });
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleRolePicker() {
|
||||||
|
this.setData({ showRolePicker: !this.data.showRolePicker });
|
||||||
|
},
|
||||||
|
|
||||||
|
async selectRole(e) {
|
||||||
|
const newRole = e.currentTarget.dataset.role;
|
||||||
|
if (newRole === this.data.currentRole) {
|
||||||
|
this.setData({ showRolePicker: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setData({ showRolePicker: false });
|
||||||
|
|
||||||
|
// 1. 立即更新角色并保存
|
||||||
|
app.globalData.currentRole = newRole;
|
||||||
|
wx.setStorageSync('currentRole', newRole);
|
||||||
|
|
||||||
|
// 2. 🔥 确定跳转页面:所有角色都去“我的”页面
|
||||||
|
let targetPage = '';
|
||||||
|
if (newRole === 'dashou') {
|
||||||
|
// 打手需要根据金牌状态决定跳转
|
||||||
|
const isJinpai = wx.getStorageSync('isJinpai') === 1;
|
||||||
|
targetPage = isJinpai ? '/pages/jinpaids/jinpaids' : '/pages/dashouduan/dashouduan';
|
||||||
|
} else {
|
||||||
|
// 其他角色直接取 tabList 最后一个页面
|
||||||
|
const cfg = roleCfg[newRole];
|
||||||
|
if (cfg && cfg.tabList.length > 0) {
|
||||||
|
targetPage = '/' + cfg.tabList[cfg.tabList.length - 1].pagePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 跳转
|
||||||
|
if (targetPage) {
|
||||||
|
wx.reLaunch({ url: targetPage });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 后台异步切换连接
|
||||||
|
app.switchRoleAndReconnect(newRole).catch(err => {
|
||||||
|
console.error('后台切换连接失败:', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
switchTab(e) {
|
||||||
|
const path = e.currentTarget.dataset.path;
|
||||||
|
const idx = e.currentTarget.dataset.index;
|
||||||
|
if (idx === this.data.selectedIndex) return;
|
||||||
|
wx.redirectTo({ url: '/' + path });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
4
custom-tab-bar/index.json
Normal file
4
custom-tab-bar/index.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
42
custom-tab-bar/index.wxml
Normal file
42
custom-tab-bar/index.wxml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<view class="custom-tab-bar">
|
||||||
|
<view class="bar-row">
|
||||||
|
<!-- 左侧椭圆身份按钮 -->
|
||||||
|
<view class="role-switch-btn" catchtap="toggleRolePicker">
|
||||||
|
<image class="role-switch-icon" src="/images/role-switch.png" mode="aspectFit" />
|
||||||
|
<text class="role-switch-text">切换</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧纯黑闪光胶囊 -->
|
||||||
|
<view class="tab-capsule">
|
||||||
|
<block wx:for="{{tabList}}" wx:key="index">
|
||||||
|
<view class="tab-item {{selectedIndex === index ? 'active' : ''}}"
|
||||||
|
data-path="{{item.pagePath}}"
|
||||||
|
data-index="{{index}}"
|
||||||
|
catchtap="switchTab">
|
||||||
|
<!-- 🔥 根据页面路径判断是否显示角标,不再写死 index -->
|
||||||
|
<view class="badge-container" wx:if="{{item.pagePath === 'pages/xiaoxi/xiaoxi' && badgeText}}">
|
||||||
|
<view class="tab-badge">{{badgeText}}</view>
|
||||||
|
</view>
|
||||||
|
<image class="tab-icon" src="{{selectedIndex === index ? item.selectedIconPath : item.iconPath}}" mode="aspectFit" />
|
||||||
|
<text class="tab-text">{{item.text}}</text>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 身份选择弹窗 -->
|
||||||
|
<view wx:if="{{showRolePicker}}" class="picker-mask" catchtap="toggleRolePicker"></view>
|
||||||
|
<view wx:if="{{showRolePicker}}" class="picker-panel">
|
||||||
|
<view class="picker-header">
|
||||||
|
<text class="picker-title">切换身份</text>
|
||||||
|
<text class="picker-close" catchtap="toggleRolePicker">✕</text>
|
||||||
|
</view>
|
||||||
|
<block wx:for="{{availableRoles}}" wx:key="key">
|
||||||
|
<view class="role-option {{currentRole === item.key ? 'active' : ''}}"
|
||||||
|
data-role="{{item.key}}" catchtap="selectRole">
|
||||||
|
<text>{{item.name}}</text>
|
||||||
|
<text wx:if="{{currentRole === item.key}}" class="check-mark">✓</text>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
165
custom-tab-bar/index.wxss
Normal file
165
custom-tab-bar/index.wxss
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/* custom-tab-bar/index.wxss - 角标浮于右上角,可超出胶囊 */
|
||||||
|
|
||||||
|
.custom-tab-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0; left: 0; right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧椭圆身份按钮 */
|
||||||
|
.role-switch-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 110rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
background: #000000;
|
||||||
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
|
||||||
|
margin-right: 20rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.role-switch-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
margin-bottom: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-switch-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #00f7ff;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 纯黑闪光胶囊,移除overflow保证角标可见 */
|
||||||
|
.tab-capsule {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 100rpx;
|
||||||
|
background: #000000;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
border: 1px solid rgba(255,255,255,0.15);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.8);
|
||||||
|
padding: 0 20rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tab-capsule::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: -100%;
|
||||||
|
width: 60%; height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.25) 20%, rgba(255,255,255,0.35) 50%, rgba(255,255,255,0.25) 80%, transparent 100%);
|
||||||
|
animation: shine 3s infinite;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
@keyframes shine {
|
||||||
|
0% { left: -100%; }
|
||||||
|
25% { left: 150%; }
|
||||||
|
100% { left: 150%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
position: relative; /* 角标的定位参考 */
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 70rpx;
|
||||||
|
}
|
||||||
|
.tab-icon {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.tab-text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #aaa;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.active .tab-icon {
|
||||||
|
opacity: 1;
|
||||||
|
filter: brightness(0) invert(1) drop-shadow(0 0 8rpx #00aaff);
|
||||||
|
}
|
||||||
|
.active .tab-text {
|
||||||
|
color: #00aaff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🔴 角标容器:绝对定位到图标右上角,允许超出 */
|
||||||
|
.badge-container {
|
||||||
|
position: absolute;
|
||||||
|
top: -10rpx; /* 往上提,更接近图标右上角 */
|
||||||
|
right: -15rpx; /* 往右伸,超出tab-item */
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: none; /* 不阻挡点击 */
|
||||||
|
}
|
||||||
|
.tab-badge {
|
||||||
|
min-width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
background: #ff3b30;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.3);
|
||||||
|
border: 1rpx solid rgba(255,255,255,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗部分保持不变 */
|
||||||
|
.picker-mask {
|
||||||
|
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
.picker-panel {
|
||||||
|
position: fixed; bottom: 0; left: 0; right: 0;
|
||||||
|
background: #000000;
|
||||||
|
border-radius: 30rpx 30rpx 0 0;
|
||||||
|
z-index: 10001;
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.picker-header {
|
||||||
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
|
padding: 30rpx 40rpx 20rpx;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.picker-title { font-size: 32rpx; color: #00f7ff; }
|
||||||
|
.picker-close { font-size: 36rpx; color: #aaa; padding: 0 10rpx; }
|
||||||
|
.role-option {
|
||||||
|
display: flex; justify-content: space-between; align-items: center;
|
||||||
|
padding: 28rpx 40rpx;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||||
|
color: #ccc; font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.role-option.active { color: #00f7ff; }
|
||||||
|
.check-mark { font-size: 32rpx; color: #00f7ff; }
|
||||||
|
|
||||||
|
page {
|
||||||
|
padding-bottom: 100rpx;
|
||||||
|
}
|
||||||
0
dengji/__init__.py
Normal file
0
dengji/__init__.py
Normal file
BIN
dengji/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
dengji/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dengji/__pycache__/apps.cpython-313.pyc
Normal file
BIN
dengji/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
3
dengji/admin.py
Normal file
3
dengji/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
dengji/apps.py
Normal file
6
dengji/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DengjiConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "dengji"
|
||||||
0
dengji/migrations/__init__.py
Normal file
0
dengji/migrations/__init__.py
Normal file
235
dengji/models.py
Normal file
235
dengji/models.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from dingdan.models import Dingdan
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 板块表 ====================
|
||||||
|
class Bankuai(models.Model):
|
||||||
|
bankuai_id = models.AutoField(primary_key=True, verbose_name='板块ID')
|
||||||
|
mingcheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='板块名称')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'bankuai'
|
||||||
|
verbose_name = '板块'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.mingcheng
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 打手订单表现记录 ====================
|
||||||
|
class DashouBiaoxian(models.Model):
|
||||||
|
dingdan = models.ForeignKey('dingdan.Dingdan', to_field='dingdan_id', on_delete=models.CASCADE, related_name='biaoxian', verbose_name='关联订单', db_index=True)
|
||||||
|
|
||||||
|
dashou_id = models.CharField(max_length=32, db_index=True, verbose_name='打手ID')
|
||||||
|
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True,null=True)
|
||||||
|
shangxian_sudu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='上线速度评价(1-5)')
|
||||||
|
fuwu_taidu = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name='服务态度评价(1-5)')
|
||||||
|
fanche_cishu = models.PositiveIntegerField(null=True, blank=True, verbose_name='翻车次数')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
jiedan_time = models.DateTimeField(null=True, blank=True, verbose_name='接单时间')
|
||||||
|
tijiao_time = models.DateTimeField(null=True, blank=True, verbose_name='提交时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'dashou_biaoxian'
|
||||||
|
verbose_name = '打手订单表现'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
indexes = [models.Index(fields=['dashou_id', 'bankuai', 'create_time'])]
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 经验变动记录 ====================
|
||||||
|
class JingyanJilu(models.Model):
|
||||||
|
yonghu_id = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||||
|
yonghu_leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='用户类型')
|
||||||
|
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', verbose_name='所属板块', db_index=True)
|
||||||
|
sudu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='上线速度经验')
|
||||||
|
shili_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='打手实力经验')
|
||||||
|
taidu_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='服务态度经验')
|
||||||
|
zonghe_jingyan = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name='综合经验')
|
||||||
|
dingdan_id = models.CharField(max_length=32, null=True, blank=True, db_index=True, verbose_name='关联订单ID')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'jingyan_jilu'
|
||||||
|
verbose_name = '经验变动记录'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
indexes = [models.Index(fields=['yonghu_id', 'bankuai', 'create_time'])]
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 称号主表 ====================
|
||||||
|
class Chenghao(models.Model):
|
||||||
|
mingcheng = models.CharField(max_length=50, unique=True, verbose_name='称号名称')
|
||||||
|
leixing = models.CharField(max_length=10, choices=[('dashou','打手'),('boss','老板'),('shangjia','商家'),('guanshi','管事'),('zuzhang','组长')], verbose_name='称号类型')
|
||||||
|
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='所属板块(空=全板块通用)')
|
||||||
|
tubiao_url = models.CharField(max_length=200, null=True, blank=True, verbose_name='称号图标相对URL')
|
||||||
|
texiao_miaoshu = models.TextField(null=True, blank=True, verbose_name='特效描述(JSON)')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
kaohe_guize = models.TextField(null=True, blank=True, verbose_name='考核规则') # 新增
|
||||||
|
kaioi_jinpai = models.BooleanField(default=False, verbose_name='是否同时开启金牌')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'chenghao'
|
||||||
|
verbose_name = '称号'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
indexes = [models.Index(fields=['leixing', 'bankuai'])]
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 用户 - 称号关联 ====================
|
||||||
|
class YonghuChenghao(models.Model):
|
||||||
|
yonghu = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chenghao_list', verbose_name='用户', db_index=True)
|
||||||
|
chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='称号')
|
||||||
|
huode_time = models.DateTimeField(auto_now_add=True, verbose_name='获得时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'yonghu_chenghao'
|
||||||
|
verbose_name = '用户称号关联'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
unique_together = ('yonghu', 'chenghao')
|
||||||
|
# 修复:只索引本表字段
|
||||||
|
indexes = [models.Index(fields=['yonghu', 'chenghao'])]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DingdanBiaoqian(models.Model):
|
||||||
|
"""订单需求标签关联表"""
|
||||||
|
dingdan = models.ForeignKey(
|
||||||
|
Dingdan,
|
||||||
|
to_field='dingdan_id', # 关联到字符串字段 dingdan_id
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name='订单',
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
chenghao = models.ForeignKey('dengji.Chenghao', on_delete=models.CASCADE, verbose_name='标签')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'dingdan_biaoqian'
|
||||||
|
verbose_name = '订单需求标签'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
unique_together = ('dingdan', 'chenghao')
|
||||||
|
indexes = [models.Index(fields=['dingdan'])]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ShenheJilu(models.Model):
|
||||||
|
"""审核记录表"""
|
||||||
|
shenqingren_id = models.CharField(max_length=7, verbose_name='申请人用户ID')
|
||||||
|
shenheguan_id = models.CharField(max_length=7, null = True,verbose_name='审核官用户ID')
|
||||||
|
zhuangtai = models.IntegerField(choices=[(0, '审核中'), (1, '通过'), (2, '未通过'), (3, '待审核')], default=0, verbose_name='审核状态')
|
||||||
|
bankuai = models.ForeignKey(Bankuai, on_delete=models.PROTECT, to_field='bankuai_id', null=True, blank=True, verbose_name='审核板块')
|
||||||
|
chenghao = models.ForeignKey(Chenghao, on_delete=models.PROTECT, null=True, blank=True, verbose_name='审核标签')
|
||||||
|
cishu = models.IntegerField(default=1, verbose_name='第几次审核')
|
||||||
|
jiaofei_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='缴纳费用')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
|
||||||
|
jilu_id = models.CharField(max_length=32, unique=True, db_index=True, verbose_name='记录ID') # 新增
|
||||||
|
guize_neirong = models.TextField(null=True, blank=True, verbose_name='规则内容')
|
||||||
|
shenheguan_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='审核官游戏ID')
|
||||||
|
dashou_youxi_id = models.CharField(max_length=50, null=True, blank=True, verbose_name='打手游戏ID')
|
||||||
|
dashou_beizhu = models.TextField(null=True, blank=True, verbose_name='打手备注')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'shenhe_jilu'
|
||||||
|
verbose_name = '审核记录'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['shenqingren_id', 'zhuangtai']),
|
||||||
|
models.Index(fields=['shenheguan_id']),
|
||||||
|
]
|
||||||
|
|
||||||
|
class KaoheJiluFeiyong(models.Model):
|
||||||
|
"""每次考核记录实际缴纳的费用明细"""
|
||||||
|
jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='考核记录')
|
||||||
|
cishu = models.IntegerField(verbose_name='第几次考核')
|
||||||
|
jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='缴纳金额')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'kaohe_jilu_feiyong'
|
||||||
|
verbose_name = '考核记录费用'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
unique_together = ('jilu', 'cishu')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class KaohePayTemp(models.Model):
|
||||||
|
"""考核支付临时上下文(仅用于支付成功后回调取参,不删除数据)"""
|
||||||
|
dingdan_id = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='订单ID')
|
||||||
|
yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID')
|
||||||
|
tag_id = models.IntegerField(verbose_name='标签ID')
|
||||||
|
game_nick = models.CharField(max_length=100, verbose_name='游戏昵称')
|
||||||
|
remark = models.TextField(blank=True, verbose_name='备注')
|
||||||
|
reapply = models.BooleanField(default=False, verbose_name='是否再次申请')
|
||||||
|
jilu_id = models.CharField(max_length=32, null=True, blank=True, verbose_name='原记录ID(再次申请时)')
|
||||||
|
shenheguan_id = models.CharField(max_length=7, blank=True, verbose_name='指定考核官ID')
|
||||||
|
cishu = models.IntegerField(verbose_name='本次考核次数(预计算)')
|
||||||
|
fee = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='本次费用(分账单位)')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'kaohe_pay_temp'
|
||||||
|
verbose_name = '考核支付临时表'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
class KaoheCishuFeiyong(models.Model):
|
||||||
|
"""各标签各考核次数所需缴纳金额"""
|
||||||
|
chenghao = models.ForeignKey(Chenghao, on_delete=models.CASCADE, verbose_name='标签')
|
||||||
|
cishu = models.IntegerField(verbose_name='第几次考核')
|
||||||
|
feiyong = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='需缴纳金额')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'kaohe_cishu_feiyong'
|
||||||
|
verbose_name = '考核次数费用'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
unique_together = ('chenghao', 'cishu')
|
||||||
|
indexes = [models.Index(fields=['chenghao', 'cishu'])]
|
||||||
|
|
||||||
|
|
||||||
|
class KaoheJujueJilu(models.Model):
|
||||||
|
"""考核拒绝原因记录"""
|
||||||
|
jilu = models.ForeignKey(ShenheJilu, on_delete=models.CASCADE, to_field='jilu_id', verbose_name='审核记录')
|
||||||
|
cishu = models.IntegerField(verbose_name='对应次数')
|
||||||
|
shenheguan_id = models.CharField(max_length=7, verbose_name='考核官用户ID')
|
||||||
|
yuanyin = models.TextField(verbose_name='拒绝原因')
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'kaohe_jujue_jilu'
|
||||||
|
verbose_name = '拒绝原因记录'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
indexes = [models.Index(fields=['jilu_id', 'cishu'])]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class KaoheguanBankuai(models.Model):
|
||||||
|
"""考核官与板块多对多关联表"""
|
||||||
|
kaoheguan_id = models.CharField(max_length=7, db_index=True, verbose_name='考核官用户ID')
|
||||||
|
bankuai = models.ForeignKey(Bankuai, on_delete=models.CASCADE, to_field='bankuai_id', verbose_name='所属板块')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'kaoheguan_bankuai'
|
||||||
|
verbose_name = '考核官板块关联'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
|
unique_together = ('kaoheguan_id', 'bankuai')
|
||||||
|
indexes = [models.Index(fields=['kaoheguan_id'])]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ShenheGuize(models.Model):
|
||||||
|
"""审核规则表"""
|
||||||
|
guize_id = models.AutoField(primary_key=True, verbose_name='规则ID')
|
||||||
|
neirong = models.TextField(verbose_name='规则内容')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'shenhe_guize'
|
||||||
|
verbose_name = '审核规则'
|
||||||
|
verbose_name_plural = verbose_name
|
||||||
87
dengji/services.py
Normal file
87
dengji/services.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# dengji/services.py
|
||||||
|
import logging
|
||||||
|
from django.db import transaction, IntegrityError
|
||||||
|
from django.utils import timezone
|
||||||
|
from .models import DashouBiaoxian
|
||||||
|
|
||||||
|
logger = logging.getLogger('dengji')
|
||||||
|
|
||||||
|
def record_dashou_biaoxian(dingdan_id, dashou_id, xingwei, **kwargs):
|
||||||
|
"""
|
||||||
|
统一记录打手订单表现
|
||||||
|
:param dingdan_id: 订单ID
|
||||||
|
:param dashou_id: 打手ID
|
||||||
|
:param xingwei: 行为类型 1-抢单 2-提交订单 3-结算
|
||||||
|
:param kwargs: 可选字段(与模型字段同名):
|
||||||
|
jiedan_time, tijiao_time,
|
||||||
|
bankuai, shangxian_sudu, fuwu_taidu, fanche_cishu
|
||||||
|
注意:不管是哪种行为,dingdan_id 和 dashou_id 都是必传的,
|
||||||
|
如果没传时间且业务需要时间,则自动取当前时间。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# ----- 抢单:创建记录 -----
|
||||||
|
if xingwei == 1:
|
||||||
|
jiedan_time = kwargs.get('jiedan_time') or timezone.now()
|
||||||
|
bankuai = kwargs.get('bankuai') # 可为 None
|
||||||
|
|
||||||
|
# 避免重复创建(同一订单只会有一条表现记录)
|
||||||
|
obj, created = DashouBiaoxian.objects.get_or_create(
|
||||||
|
dingdan_id=dingdan_id,
|
||||||
|
dashou_id=dashou_id,
|
||||||
|
defaults={
|
||||||
|
'bankuai': bankuai,
|
||||||
|
'jiedan_time': jiedan_time,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if not created:
|
||||||
|
logger.info(f"表现记录已存在,跳过创建: dingdan={dingdan_id}, dashou={dashou_id}")
|
||||||
|
else:
|
||||||
|
logger.info(f"创建抢单表现记录: dingdan={dingdan_id}, dashou={dashou_id}")
|
||||||
|
|
||||||
|
# ----- 提交订单:更新提交时间 -----
|
||||||
|
elif xingwei == 2:
|
||||||
|
tijiao_time = kwargs.get('tijiao_time') or timezone.now()
|
||||||
|
# 只更新已存在的记录,找不到就直接跳过
|
||||||
|
updated = DashouBiaoxian.objects.filter(
|
||||||
|
dingdan_id=dingdan_id,
|
||||||
|
dashou_id=dashou_id
|
||||||
|
).update(tijiao_time=tijiao_time)
|
||||||
|
if updated:
|
||||||
|
logger.info(f"更新提交时间: dingdan={dingdan_id}, dashou={dashou_id}")
|
||||||
|
else:
|
||||||
|
logger.info(f"无对应记录,跳过提交时间更新: dingdan={dingdan_id}, dashou={dashou_id}")
|
||||||
|
|
||||||
|
# ----- 结算:更新评价信息 -----
|
||||||
|
elif xingwei == 3:
|
||||||
|
# 构建要更新的字段,只更新 kwargs 中传入的非 None 字段
|
||||||
|
update_fields = {}
|
||||||
|
for field in ['shangxian_sudu', 'fuwu_taidu', 'fanche_cishu']:
|
||||||
|
val = kwargs.get(field)
|
||||||
|
if val is not None:
|
||||||
|
update_fields[field] = val
|
||||||
|
# 如果还传了板块也可以更新
|
||||||
|
bankuai = kwargs.get('bankuai')
|
||||||
|
if bankuai is not None:
|
||||||
|
update_fields['bankuai'] = bankuai
|
||||||
|
|
||||||
|
if update_fields:
|
||||||
|
updated = DashouBiaoxian.objects.filter(
|
||||||
|
dingdan_id=dingdan_id,
|
||||||
|
dashou_id=dashou_id
|
||||||
|
).update(**update_fields)
|
||||||
|
if updated:
|
||||||
|
logger.info(f"更新评价信息: dingdan={dingdan_id}, dashou={dashou_id}, fields={update_fields}")
|
||||||
|
else:
|
||||||
|
logger.info(f"无对应记录,跳过评价更新: dingdan={dingdan_id}, dashou={dashou_id}")
|
||||||
|
else:
|
||||||
|
logger.info(f"结算时未传入任何评价字段,跳过更新")
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"未知的行为类型: {xingwei}")
|
||||||
|
|
||||||
|
except IntegrityError as e:
|
||||||
|
# 唯一约束冲突(get_or_create 理论上已处理,但并发时仍可能撞上)
|
||||||
|
logger.warning(f"表现记录写入冲突,可能并发创建: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
# 无论发生什么错误,都不影响主流程
|
||||||
|
logger.error(f"记录打手表现异常: dingdan={dingdan_id}, dashou={dashou_id}, xingwei={xingwei}, error={e}", exc_info=True)
|
||||||
3
dengji/tests.py
Normal file
3
dengji/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
22
dengji/urls.py
Normal file
22
dengji/urls.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from .views import DashouBiaoqianView, BankuaiBiaoqianView, KaoheguanRegisterView, \
|
||||||
|
KaoheguanRefreshView, ShenheDatingModuleView, ShenheDatingDataView, ShenheQiangdanView, ShenheCaozuoView, \
|
||||||
|
ShenheXiangqingView, KaoheJinpaiTagsView, KaoheJiluView, KaoheFreeApplyView, KaoheJiluListView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('dsbqhq', DashouBiaoqianView.as_view(), name='打手标签获取'),
|
||||||
|
# 其他已有路由保持不变
|
||||||
|
path('bkqhbq', BankuaiBiaoqianView.as_view(), name='抢单页面获取标签'),
|
||||||
|
path('khgzc', KaoheguanRegisterView.as_view(), name='审核官注册'),
|
||||||
|
path('khgsx', KaoheguanRefreshView.as_view(), name='审核官刷新'),
|
||||||
|
path('shdthqmk', ShenheDatingModuleView.as_view(), name='审核官获取板块'),
|
||||||
|
path('hqshsj', ShenheDatingDataView.as_view(), name='审核官获取待审核列表'),
|
||||||
|
path('shqd', ShenheQiangdanView.as_view(), name='审核官接待审核'),
|
||||||
|
path('hqkhxq', ShenheXiangqingView.as_view(), name='获取审核内容'),
|
||||||
|
path('khgxgkh', ShenheCaozuoView.as_view(), name='修改审核状态'),
|
||||||
|
|
||||||
|
path('dskhhq', KaoheJinpaiTagsView.as_view(), name='考核金牌页面标签'),
|
||||||
|
path('dskhjl', KaoheJiluView.as_view(), name='打手考核记录'),
|
||||||
|
path('dssqkh', KaoheFreeApplyView.as_view(), name='免费考核申请接口'),
|
||||||
|
path('khghqkhjl', KaoheJiluListView.as_view(), name='考核官获取考核记录'),
|
||||||
|
]
|
||||||
403
dengji/utils.py
Normal file
403
dengji/utils.py
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
# dingdan/utils.py
|
||||||
|
from .models import DingdanBiaoqian
|
||||||
|
from yonghu.models import UserMain
|
||||||
|
from dengji.models import YonghuChenghao
|
||||||
|
|
||||||
|
def check_dashou_biaoqian_required(order_id, dashou_yonghuid):
|
||||||
|
"""
|
||||||
|
检查打手是否满足订单的标签需求。
|
||||||
|
- 如果订单没有需求标签,直接放行。
|
||||||
|
- 如果有,则要求打手至少拥有其中一个标签。
|
||||||
|
返回 (allowed: bool, message: str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 获取订单的需求标签ID集合
|
||||||
|
required_ids = set(
|
||||||
|
DingdanBiaoqian.objects.filter(dingdan_id=order_id)
|
||||||
|
.values_list('chenghao_id', flat=True)
|
||||||
|
)
|
||||||
|
if not required_ids:
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
# 2. 找到打手对应的用户记录
|
||||||
|
try:
|
||||||
|
user = UserMain.objects.get(yonghuid=dashou_yonghuid)
|
||||||
|
except UserMain.DoesNotExist:
|
||||||
|
return False, '用户信息异常'
|
||||||
|
|
||||||
|
# 3. 获取打手已拥有的标签ID集合(只取打手类型)
|
||||||
|
owned_ids = set(
|
||||||
|
YonghuChenghao.objects.filter(
|
||||||
|
yonghu=user,
|
||||||
|
chenghao__leixing='dashou'
|
||||||
|
).values_list('chenghao_id', flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not owned_ids:
|
||||||
|
return False, '您未拥有任何打手标签,无法抢此订单'
|
||||||
|
|
||||||
|
# 4. 交集判断
|
||||||
|
if required_ids.intersection(owned_ids):
|
||||||
|
return True, ''
|
||||||
|
else:
|
||||||
|
return False, '您的标签不符合此订单要求,无法抢单'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 标签校验失败不影响主流程,记录日志并保守放行(也可保守拒绝,按需求选择)
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('dingdan')
|
||||||
|
logger.error(f"标签校验异常: {e}", exc_info=True)
|
||||||
|
# 安全起见,如果校验出错,选择拒绝,避免越权
|
||||||
|
return False, '订单标签校验异常,请稍后重试'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# dengji/utils.py
|
||||||
|
from .models import Chenghao, KaoheCishuFeiyong, ShenheJilu, KaoheJiluFeiyong, YonghuChenghao, KaoheguanBankuai
|
||||||
|
from yonghu.models import UserMain, UserShenheguan, UserDashou
|
||||||
|
from django.db import models
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.db.models import Q, Max
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import logging
|
||||||
|
from django.db.models import F, Max
|
||||||
|
logger = logging.getLogger('dengji')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_tag_fee(chenghao, cishu):
|
||||||
|
"""
|
||||||
|
获取标签在指定考核次数下的费用。
|
||||||
|
- 若有对应次数的配置,直接返回。
|
||||||
|
- 若没有该次数,取该标签下已有最大次数的费用(作为后续次数的价格)。
|
||||||
|
- 若该标签完全没有费用记录,返回 0.00(免费)。
|
||||||
|
"""
|
||||||
|
fee_obj = KaoheCishuFeiyong.objects.filter(chenghao=chenghao, cishu=cishu).first()
|
||||||
|
if fee_obj:
|
||||||
|
return fee_obj.feiyong
|
||||||
|
|
||||||
|
max_obj = KaoheCishuFeiyong.objects.filter(chenghao=chenghao).order_by('-cishu').first()
|
||||||
|
if max_obj:
|
||||||
|
return max_obj.feiyong
|
||||||
|
|
||||||
|
return Decimal('0.00')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_unique_jilu_id():
|
||||||
|
"""
|
||||||
|
生成唯一的审核记录 ID,格式:SH + 时间戳(10位) + 随机数(4位)
|
||||||
|
循环检查数据库避免重复,最多尝试 10 次。
|
||||||
|
"""
|
||||||
|
for _ in range(10):
|
||||||
|
timestamp = str(int(time.time() * 1000))[-10:]
|
||||||
|
random_part = str(random.randint(1000, 9999))
|
||||||
|
jilu_id = f'SH{timestamp}{random_part}'
|
||||||
|
if not ShenheJilu.objects.filter(jilu_id=jilu_id).exists():
|
||||||
|
return jilu_id
|
||||||
|
raise Exception('无法生成唯一记录ID,请稍后重试')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_shenheguan(shenheguan_id, bankuai, fee, applicant_user):
|
||||||
|
"""
|
||||||
|
校验指定考核官是否合法
|
||||||
|
- 考核官必须存在且未被禁用
|
||||||
|
- 必须属于对应板块
|
||||||
|
- 必须处于空闲状态
|
||||||
|
- 若费用>0,则要求已认证
|
||||||
|
返回 (valid, error_msg)
|
||||||
|
"""
|
||||||
|
if not shenheguan_id:
|
||||||
|
return True, ''
|
||||||
|
try:
|
||||||
|
kg = UserShenheguan.objects.select_related('user').get(
|
||||||
|
user__yonghuid=shenheguan_id,
|
||||||
|
zhuangtai=1
|
||||||
|
)
|
||||||
|
except UserShenheguan.DoesNotExist:
|
||||||
|
return False, '考核官不存在或已被禁用'
|
||||||
|
|
||||||
|
# 板块权限
|
||||||
|
if not KaoheguanBankuai.objects.filter(kaoheguan_id=shenheguan_id, bankuai=bankuai).exists():
|
||||||
|
return False, '该考核官不属于此板块'
|
||||||
|
|
||||||
|
# 空闲状态
|
||||||
|
if kg.shenhe_zhuangtai != 0:
|
||||||
|
return False, '该考核官当前忙碌,无法指定'
|
||||||
|
|
||||||
|
# 费用>0需要认证
|
||||||
|
if float(fee) > 0 and not kg.is_renzheng:
|
||||||
|
return False, '费用大于0时只能指定已认证的考核官'
|
||||||
|
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
|
||||||
|
def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, shenheguan_id=''):
|
||||||
|
"""
|
||||||
|
创建或更新审核记录。
|
||||||
|
- 首次申请:创建新记录,生成唯一 jilu_id。
|
||||||
|
- 再次申请:基于原记录(必须状态为未通过)更新次数、累加总费用、更新备注和游戏昵称,
|
||||||
|
保留审核官信息,状态重置为待审核。不创建新记录。
|
||||||
|
- 每次申请均创建一条费用明细记录(KaoheJiluFeiyong),记录本次缴费。
|
||||||
|
- 若原记录有考核官,则将其状态改为审核中。
|
||||||
|
- 新增 shenheguan_id 参数:指定考核官ID,仅在原记录无考核官时可传入。
|
||||||
|
返回 (success, message, record)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 1. 校验标签存在
|
||||||
|
try:
|
||||||
|
chenghao = Chenghao.objects.get(id=tag_id)
|
||||||
|
except Chenghao.DoesNotExist:
|
||||||
|
return False, '标签不存在', None
|
||||||
|
|
||||||
|
# 2. 判断用户是否已拥有该标签
|
||||||
|
if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists():
|
||||||
|
return False, '您已拥有该标签,无需重复考核', None
|
||||||
|
|
||||||
|
# 3. 检查是否有进行中的审核记录(待审核、审核中)
|
||||||
|
has_pending = ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
chenghao=chenghao,
|
||||||
|
zhuangtai__in=[0, 3]
|
||||||
|
).exists()
|
||||||
|
if has_pending:
|
||||||
|
return False, '您有未完成的考核申请,请等待结果', None
|
||||||
|
|
||||||
|
last_record = None
|
||||||
|
current_fee = Decimal('0.00')
|
||||||
|
|
||||||
|
# 4. 处理再次申请
|
||||||
|
if reapply and jilu_id:
|
||||||
|
# 查询原记录(未通过状态)
|
||||||
|
last_record = ShenheJilu.objects.filter(
|
||||||
|
jilu_id=jilu_id,
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai=2
|
||||||
|
).first()
|
||||||
|
if not last_record:
|
||||||
|
return False, '原记录不存在或状态不是未通过', None
|
||||||
|
|
||||||
|
new_cishu = last_record.cishu + 1
|
||||||
|
current_fee = get_tag_fee(chenghao, new_cishu)
|
||||||
|
|
||||||
|
# 确定最终状态:有考核官 = 审核中(0),否则待审核(3)
|
||||||
|
final_kg_id = last_record.shenheguan_id # 保留原来的
|
||||||
|
if shenheguan_id and not last_record.shenheguan_id:
|
||||||
|
final_kg_id = shenheguan_id # 新指定
|
||||||
|
|
||||||
|
final_zhuangtai = 0 if final_kg_id else 3 # 有考核官就是审核中
|
||||||
|
if final_zhuangtai == 3:
|
||||||
|
final_kg_id = None
|
||||||
|
|
||||||
|
# 原子化更新
|
||||||
|
ShenheJilu.objects.filter(pk=last_record.pk).update(
|
||||||
|
cishu=new_cishu,
|
||||||
|
jiaofei_jine=F('jiaofei_jine') + current_fee,
|
||||||
|
dashou_youxi_id=game_nick,
|
||||||
|
dashou_beizhu=remark,
|
||||||
|
guize_neirong=chenghao.kaohe_guize or '',
|
||||||
|
zhuangtai=final_zhuangtai,
|
||||||
|
shenheguan_id=final_kg_id,
|
||||||
|
)
|
||||||
|
last_record.refresh_from_db()
|
||||||
|
record = last_record
|
||||||
|
|
||||||
|
# 如果原考核官被替换或清除,释放旧考核官
|
||||||
|
if last_record.shenheguan_id != final_kg_id:
|
||||||
|
if last_record.shenheguan_id:
|
||||||
|
UserShenheguan.objects.filter(
|
||||||
|
user__yonghuid=last_record.shenheguan_id
|
||||||
|
).update(shenhe_zhuangtai=0)
|
||||||
|
else:
|
||||||
|
# 首次申请
|
||||||
|
max_cishu = ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
chenghao=chenghao,
|
||||||
|
zhuangtai__in=[1, 2]
|
||||||
|
).aggregate(max=Max('cishu'))['max'] or 0
|
||||||
|
new_cishu = max_cishu + 1
|
||||||
|
current_fee = get_tag_fee(chenghao, new_cishu)
|
||||||
|
|
||||||
|
bankuai = chenghao.bankuai
|
||||||
|
rule_text = chenghao.kaohe_guize or ''
|
||||||
|
new_jilu_id = generate_unique_jilu_id()
|
||||||
|
|
||||||
|
initial_zhuangtai = 0 if shenheguan_id else 3 # 指定→审核中,否则待审核
|
||||||
|
record = ShenheJilu.objects.create(
|
||||||
|
jilu_id=new_jilu_id,
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai=initial_zhuangtai,
|
||||||
|
bankuai=bankuai,
|
||||||
|
chenghao=chenghao,
|
||||||
|
cishu=new_cishu,
|
||||||
|
jiaofei_jine=current_fee,
|
||||||
|
dashou_youxi_id=game_nick,
|
||||||
|
dashou_beizhu=remark,
|
||||||
|
guize_neirong=rule_text,
|
||||||
|
shenheguan_id=shenheguan_id if shenheguan_id else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. 创建费用明细(防重复)
|
||||||
|
KaoheJiluFeiyong.objects.get_or_create(
|
||||||
|
jilu=record,
|
||||||
|
cishu=record.cishu,
|
||||||
|
defaults={'jine': current_fee}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6. 更新考核官统计(仅当状态为审核中且有考核官)
|
||||||
|
if record.zhuangtai == 0 and record.shenheguan_id:
|
||||||
|
UserShenheguan.objects.filter(
|
||||||
|
user__yonghuid=record.shenheguan_id
|
||||||
|
).update(
|
||||||
|
shenhe_zongshu=F('shenhe_zongshu') + 1,
|
||||||
|
shenhe_zhuangtai=1
|
||||||
|
)
|
||||||
|
|
||||||
|
return True, '申请成功', record
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建审核记录异常: {e}", exc_info=True)
|
||||||
|
return False, '系统异常', None
|
||||||
|
|
||||||
|
|
||||||
|
def create_shenhe_jilu_from_temp(user, temp_data):
|
||||||
|
"""
|
||||||
|
根据临时表数据创建审核记录(专供支付回调使用)
|
||||||
|
参数 temp_data 是 KaohePayTemp 对象
|
||||||
|
返回 (success, message, record)
|
||||||
|
"""
|
||||||
|
from .models import ShenheJilu, KaoheJiluFeiyong, Chenghao, YonghuChenghao
|
||||||
|
from django.db.models import F
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('dengji')
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"开始从临时表创建审核记录: 用户={user.yonghuid}, 订单={temp_data.dingdan_id}")
|
||||||
|
|
||||||
|
chenghao = Chenghao.objects.get(id=temp_data.tag_id)
|
||||||
|
logger.info(f"获取标签成功: {chenghao.mingcheng}")
|
||||||
|
|
||||||
|
# 检查是否已拥有标签
|
||||||
|
if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists():
|
||||||
|
logger.warning(f"用户 {user.yonghuid} 已拥有标签 {chenghao.mingcheng}")
|
||||||
|
return False, '您已拥有该标签,无需重复考核', None
|
||||||
|
|
||||||
|
# 检查是否有进行中的审核
|
||||||
|
if ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
chenghao=chenghao,
|
||||||
|
zhuangtai__in=[0, 3]
|
||||||
|
).exists():
|
||||||
|
logger.warning(f"用户 {user.yonghuid} 有未完成的考核申请")
|
||||||
|
return False, '您有未完成的考核申请,请等待结果', None
|
||||||
|
|
||||||
|
# 使用临时表中预计算好的次数和费用
|
||||||
|
new_cishu = temp_data.cishu
|
||||||
|
current_fee = temp_data.fee
|
||||||
|
reapply = temp_data.reapply
|
||||||
|
jilu_id = temp_data.jilu_id
|
||||||
|
shenheguan_id = temp_data.shenheguan_id
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"临时表数据: cishu={new_cishu}, fee={current_fee}, reapply={reapply}, jilu_id={jilu_id}, shenheguan_id={shenheguan_id}")
|
||||||
|
|
||||||
|
# 再次申请校验
|
||||||
|
if reapply:
|
||||||
|
if not jilu_id:
|
||||||
|
logger.error("再次申请但未提供原记录ID")
|
||||||
|
return False, '再次申请必须提供原记录ID', None
|
||||||
|
last = ShenheJilu.objects.filter(
|
||||||
|
jilu_id=jilu_id,
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai=2
|
||||||
|
).first()
|
||||||
|
if not last:
|
||||||
|
logger.error(f"原记录不存在或状态不是未通过: jilu_id={jilu_id}")
|
||||||
|
return False, '原记录不存在或状态不是未通过', None
|
||||||
|
if new_cishu != last.cishu + 1:
|
||||||
|
logger.error(f"考核次数不一致: 临时表={new_cishu}, 原记录次数={last.cishu}")
|
||||||
|
return False, '考核次数不一致', None
|
||||||
|
logger.info(f"再次申请校验通过,原记录ID: {jilu_id}")
|
||||||
|
|
||||||
|
# 创建或更新审核记录
|
||||||
|
if reapply and jilu_id:
|
||||||
|
# 更新原记录
|
||||||
|
final_kg_id = last.shenheguan_id
|
||||||
|
if shenheguan_id and not last.shenheguan_id:
|
||||||
|
final_kg_id = shenheguan_id
|
||||||
|
final_zhuangtai = 0 if final_kg_id else 3
|
||||||
|
if final_zhuangtai == 3:
|
||||||
|
final_kg_id = None
|
||||||
|
|
||||||
|
ShenheJilu.objects.filter(pk=last.pk).update(
|
||||||
|
cishu=new_cishu,
|
||||||
|
jiaofei_jine=F('jiaofei_jine') + current_fee,
|
||||||
|
dashou_youxi_id=temp_data.game_nick,
|
||||||
|
dashou_beizhu=temp_data.remark,
|
||||||
|
guize_neirong=chenghao.kaohe_guize or '',
|
||||||
|
zhuangtai=final_zhuangtai,
|
||||||
|
shenheguan_id=final_kg_id,
|
||||||
|
)
|
||||||
|
last.refresh_from_db()
|
||||||
|
record = last
|
||||||
|
|
||||||
|
# 释放旧考核官
|
||||||
|
if last.shenheguan_id != final_kg_id and last.shenheguan_id:
|
||||||
|
UserShenheguan.objects.filter(user__yonghuid=last.shenheguan_id).update(shenhe_zhuangtai=0)
|
||||||
|
logger.info(f"释放旧考核官: {last.shenheguan_id}")
|
||||||
|
|
||||||
|
logger.info(f"更新审核记录成功: jilu_id={record.jilu_id}, 新次数={new_cishu}")
|
||||||
|
else:
|
||||||
|
# 首次申请
|
||||||
|
bankuai = chenghao.bankuai
|
||||||
|
rule_text = chenghao.kaohe_guize or ''
|
||||||
|
new_jilu_id = generate_unique_jilu_id()
|
||||||
|
initial_zhuangtai = 0 if shenheguan_id else 3
|
||||||
|
|
||||||
|
record = ShenheJilu.objects.create(
|
||||||
|
jilu_id=new_jilu_id,
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai=initial_zhuangtai,
|
||||||
|
bankuai=bankuai,
|
||||||
|
chenghao=chenghao,
|
||||||
|
cishu=new_cishu,
|
||||||
|
jiaofei_jine=current_fee,
|
||||||
|
dashou_youxi_id=temp_data.game_nick,
|
||||||
|
dashou_beizhu=temp_data.remark,
|
||||||
|
guize_neirong=rule_text,
|
||||||
|
shenheguan_id=shenheguan_id if shenheguan_id else None,
|
||||||
|
)
|
||||||
|
logger.info(f"创建审核记录成功: jilu_id={new_jilu_id}, 状态={initial_zhuangtai}")
|
||||||
|
|
||||||
|
# 记录费用明细
|
||||||
|
fee_detail, created = KaoheJiluFeiyong.objects.get_or_create(
|
||||||
|
jilu=record,
|
||||||
|
cishu=record.cishu,
|
||||||
|
defaults={'jine': current_fee}
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
logger.info(f"创建费用明细成功: 记录ID={record.jilu_id}, 金额={current_fee}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"费用明细已存在: 记录ID={record.jilu_id}, 次数={record.cishu}")
|
||||||
|
|
||||||
|
# 更新考核官状态
|
||||||
|
if record.zhuangtai == 0 and record.shenheguan_id:
|
||||||
|
UserShenheguan.objects.filter(user__yonghuid=record.shenheguan_id).update(
|
||||||
|
shenhe_zongshu=F('shenhe_zongshu') + 1,
|
||||||
|
shenhe_zhuangtai=1
|
||||||
|
)
|
||||||
|
logger.info(f"更新考核官状态: {record.shenheguan_id}, 审核总数+1, 状态=忙碌")
|
||||||
|
|
||||||
|
logger.info(f"从临时表创建审核记录完成: jilu_id={record.jilu_id}")
|
||||||
|
return True, '申请成功', record
|
||||||
|
|
||||||
|
except Chenghao.DoesNotExist:
|
||||||
|
logger.error(f"标签不存在: tag_id={temp_data.tag_id}")
|
||||||
|
return False, '标签不存在', None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"从临时表创建审核记录异常: {str(e)}", exc_info=True)
|
||||||
|
return False, '系统异常', None
|
||||||
975
dengji/views.py
Normal file
975
dengji/views.py
Normal file
@@ -0,0 +1,975 @@
|
|||||||
|
# dengji/views.py
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from shangpin.models import ShangpinLeixing
|
||||||
|
from .models import YonghuChenghao, Chenghao, KaoheCishuFeiyong, KaoheJujueJilu
|
||||||
|
from .utils import get_tag_fee, create_shenhe_jilu
|
||||||
|
|
||||||
|
logger = logging.getLogger('dengji')
|
||||||
|
|
||||||
|
|
||||||
|
class DashouBiaoqianView(APIView):
|
||||||
|
"""获取当前打手所有称号标签"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
# 查询用户的所有称号关联,一次连表取出
|
||||||
|
relations = YonghuChenghao.objects.filter(
|
||||||
|
yonghu=yonghu
|
||||||
|
).select_related('chenghao')
|
||||||
|
|
||||||
|
chenghao_list = []
|
||||||
|
for rel in relations:
|
||||||
|
ch = rel.chenghao
|
||||||
|
# 解析特效描述 JSON(前端需要对象)
|
||||||
|
texiao_json = {}
|
||||||
|
if ch.texiao_miaoshu:
|
||||||
|
try:
|
||||||
|
texiao_json = json.loads(ch.texiao_miaoshu)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
texiao_json = {}
|
||||||
|
chenghao_list.append({
|
||||||
|
'mingcheng': ch.mingcheng,
|
||||||
|
'tubiao_url': ch.tubiao_url or '',
|
||||||
|
'texiao_json': texiao_json,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'code': 0,
|
||||||
|
'msg': '成功',
|
||||||
|
'data': {'chenghao_list': chenghao_list}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取打手标签失败: {e}", exc_info=True)
|
||||||
|
return Response({'code': 1, 'msg': '服务器错误', 'data': None})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from shangpin.models import ShangpinLeixing
|
||||||
|
from .models import YonghuChenghao, Chenghao
|
||||||
|
class BankuaiBiaoqianView(APIView):
|
||||||
|
"""获取商品类型所属板块的所有标签"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
leixing_id = request.data.get('leixing_id')
|
||||||
|
if not leixing_id:
|
||||||
|
return Response({'code': 1, 'msg': '缺少leixing_id'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
leixing = ShangpinLeixing.objects.select_related('bankuai').get(id=leixing_id)
|
||||||
|
except ShangpinLeixing.DoesNotExist:
|
||||||
|
return Response({'code': 2, 'msg': '商品类型不存在'})
|
||||||
|
|
||||||
|
bankuai = leixing.bankuai
|
||||||
|
if not bankuai:
|
||||||
|
return Response({'code': 0, 'data': {'biaoqian_list': []}})
|
||||||
|
|
||||||
|
# 获取该板块下所有类型的标签(leixing可以为dashou/shangjia等,这里先返回所有)
|
||||||
|
chenghao_list = Chenghao.objects.filter(bankuai=bankuai)
|
||||||
|
data = []
|
||||||
|
for ch in chenghao_list:
|
||||||
|
texiao_json = {}
|
||||||
|
if ch.texiao_miaoshu:
|
||||||
|
try:
|
||||||
|
texiao_json = json.loads(ch.texiao_miaoshu)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
data.append({
|
||||||
|
'id': ch.id,
|
||||||
|
'mingcheng': ch.mingcheng,
|
||||||
|
'texiao_json': texiao_json
|
||||||
|
})
|
||||||
|
return Response({'code': 0, 'data': {'biaoqian_list': data}})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# dengji/views.py
|
||||||
|
import logging
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from .models import KaoheguanBankuai, Bankuai
|
||||||
|
from django.db import transaction
|
||||||
|
from yonghu.models import UserShenheguan, UserMain, UserDashou
|
||||||
|
|
||||||
|
logger = logging.getLogger('dengji')
|
||||||
|
|
||||||
|
# 硬编码邀请码
|
||||||
|
KAOHEGUAN_INVITE_CODE = 'KHG2026VIP'
|
||||||
|
|
||||||
|
class KaoheguanRegisterView(APIView):
|
||||||
|
"""
|
||||||
|
考核官注册接口
|
||||||
|
POST /dengji/khgzc
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
invite_code = request.data.get('inviteCode', '').strip()
|
||||||
|
|
||||||
|
if not invite_code:
|
||||||
|
return Response({'code': 400, 'msg': '邀请码不能为空'})
|
||||||
|
if invite_code != KAOHEGUAN_INVITE_CODE:
|
||||||
|
return Response({'code': 400, 'msg': '邀请码错误'})
|
||||||
|
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu).first()
|
||||||
|
if kg:
|
||||||
|
if kg.zhuangtai == 1:
|
||||||
|
return Response({'code': 200, 'msg': '您已是考核官', 'data': self._build_data(kg)})
|
||||||
|
else:
|
||||||
|
return Response({'code': 400, 'msg': '账号已被封禁'})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
kg = UserShenheguan.objects.create(
|
||||||
|
user=yonghu,
|
||||||
|
zhuangtai=1,
|
||||||
|
shenhe_zhuangtai=0,
|
||||||
|
shenhe_zongshu=0,
|
||||||
|
tongguo_zongshu=0,
|
||||||
|
yue=0.00,
|
||||||
|
zonge=0.00
|
||||||
|
)
|
||||||
|
return Response({'code': 200, 'msg': '注册成功', 'data': self._build_data(kg)})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"考核官注册失败: {e}", exc_info=True)
|
||||||
|
return Response({'code': 500, 'msg': '服务器内部错误'})
|
||||||
|
|
||||||
|
def _build_data(self, kg):
|
||||||
|
bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai')
|
||||||
|
bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs]
|
||||||
|
return {
|
||||||
|
'zhuangtai': kg.zhuangtai,
|
||||||
|
'shenhe_zongshu': kg.shenhe_zongshu,
|
||||||
|
'tongguo_zongshu': kg.tongguo_zongshu,
|
||||||
|
'yue': str(kg.yue),
|
||||||
|
'zonge': str(kg.zonge),
|
||||||
|
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
|
||||||
|
'bankuai_list': bankuai_list,
|
||||||
|
'kaoheguanstatus': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class KaoheguanRefreshView(APIView):
|
||||||
|
"""
|
||||||
|
考核官数据刷新接口
|
||||||
|
POST /dengji/khgsx
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu).first()
|
||||||
|
if not kg:
|
||||||
|
return Response({'code': 400, 'msg': '您还未注册考核官'})
|
||||||
|
|
||||||
|
if kg.zhuangtai != 1:
|
||||||
|
return Response({'code': 200, 'data': {'zhuangtai': kg.zhuangtai, 'kaoheguanstatus': 0}})
|
||||||
|
|
||||||
|
# 查询所属板块
|
||||||
|
bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai')
|
||||||
|
bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs]
|
||||||
|
|
||||||
|
# 统计待审核 / 审核中的记录数量
|
||||||
|
pending_count = ShenheJilu.objects.filter(
|
||||||
|
shenheguan_id=yonghu.yonghuid,
|
||||||
|
zhuangtai=0 # 审核中
|
||||||
|
).count()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'zhuangtai': kg.zhuangtai,
|
||||||
|
'shenhe_zongshu': kg.shenhe_zongshu,
|
||||||
|
'tongguo_zongshu': kg.tongguo_zongshu,
|
||||||
|
'yue': str(kg.yue),
|
||||||
|
'zonge': str(kg.zonge),
|
||||||
|
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
|
||||||
|
'bankuai_list': bankuai_list,
|
||||||
|
'kaoheguanstatus': 1,
|
||||||
|
'is_renzheng': kg.is_renzheng, # 🆕 是否认证
|
||||||
|
'pending_shenhe_count': pending_count # 🆕 审核中数量
|
||||||
|
}
|
||||||
|
return Response({'code': 200, 'data': data})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"考核官刷新失败: {e}", exc_info=True)
|
||||||
|
return Response({'code': 500, 'msg': '服务器内部错误'})
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
from django.db import transaction, IntegrityError
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from .models import Bankuai, ShenheJilu, KaoheguanBankuai, Chenghao
|
||||||
|
|
||||||
|
class ShenheDatingModuleView(APIView):
|
||||||
|
"""审核大厅获取模块列表(板块 + 待审核数量 + 是否关联)"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
related_ids = set(KaoheguanBankuai.objects.filter(kaoheguan_id=yonghu.yonghuid)
|
||||||
|
.values_list('bankuai_id', flat=True))
|
||||||
|
|
||||||
|
bankuai_list = []
|
||||||
|
for bk in Bankuai.objects.all():
|
||||||
|
count = ShenheJilu.objects.filter(bankuai=bk, zhuangtai=3, shenheguan_id__isnull=True).count()
|
||||||
|
bankuai_list.append({
|
||||||
|
'bankuai_id': bk.bankuai_id,
|
||||||
|
'mingcheng': bk.mingcheng,
|
||||||
|
'count': count,
|
||||||
|
'is_related': bk.bankuai_id in related_ids
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response({'code': 0, 'data': {'modules': bankuai_list}})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
class ShenheDatingDataView(APIView):
|
||||||
|
"""获取审核大厅待审核记录列表"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
data = request.data
|
||||||
|
bankuai_id = data.get('bankuai_id')
|
||||||
|
page = int(data.get('page', 1))
|
||||||
|
page_size = int(data.get('page_size', 5))
|
||||||
|
|
||||||
|
query = ShenheJilu.objects.filter(zhuangtai=3, shenheguan_id__isnull=True)
|
||||||
|
if bankuai_id:
|
||||||
|
query = query.filter(bankuai_id=bankuai_id)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
jilu_list = query.select_related('bankuai', 'chenghao').order_by('-create_time')[
|
||||||
|
offset:offset + page_size]
|
||||||
|
|
||||||
|
records = []
|
||||||
|
for jl in jilu_list:
|
||||||
|
user = UserMain.objects.filter(yonghuid=jl.shenqingren_id).first()
|
||||||
|
avatar = user.avatar if user else ''
|
||||||
|
texiao_json = {}
|
||||||
|
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||||||
|
try:
|
||||||
|
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 新增字段:本次考核费用
|
||||||
|
kaohe_feiyong = float(jl.jiaofei_jine) if jl.jiaofei_jine is not None else 0.0
|
||||||
|
|
||||||
|
records.append({
|
||||||
|
'jilu_id': jl.jilu_id,
|
||||||
|
'shenqingren_id': jl.shenqingren_id,
|
||||||
|
'shenqingren_avatar': avatar,
|
||||||
|
'dashou_youxi_id': jl.dashou_youxi_id or '',
|
||||||
|
'dashou_beizhu': jl.dashou_beizhu or '',
|
||||||
|
'bankuai_id': jl.bankuai.bankuai_id if jl.bankuai else None,
|
||||||
|
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
|
||||||
|
'chenghao_id': jl.chenghao_id,
|
||||||
|
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
|
||||||
|
'chenghao_texiao_json': texiao_json,
|
||||||
|
'guize_neirong': jl.guize_neirong or '',
|
||||||
|
'kaohe_feiyong': kaohe_feiyong, # 新增
|
||||||
|
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
|
||||||
|
})
|
||||||
|
|
||||||
|
has_more = (page * page_size) < total
|
||||||
|
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
class ShenheQiangdanView(APIView):
|
||||||
|
"""
|
||||||
|
审核官抢单(接审核任务)
|
||||||
|
校验:身份、账号封禁、已有进行中审核、记录状态、不能审核自己、板块权限
|
||||||
|
成功后更新考核官扩展表:审核总数+1,审核状态设为“审核中”
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
jilu_id = request.data.get('jilu_id')
|
||||||
|
if not jilu_id:
|
||||||
|
return Response({'code': 400, 'msg': '记录ID不能为空'})
|
||||||
|
|
||||||
|
# 1. 检查考核官身份及账号是否被禁用
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||||||
|
if not kg:
|
||||||
|
return Response({'code': 403, 'msg': '您不是考核官或账号已被禁用'})
|
||||||
|
|
||||||
|
# 2. 检查是否有正在审核中的记录(zhuangtai=0),防止同时审核多个
|
||||||
|
ongoing = ShenheJilu.objects.filter(
|
||||||
|
shenheguan_id=yonghu.yonghuid, zhuangtai=0
|
||||||
|
).exists()
|
||||||
|
if ongoing:
|
||||||
|
return Response({'code': 400, 'msg': '您有正在审核中的任务,请完成后再接新任务'})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# 3. 行锁抢单:仅当状态为待审核(3)且无审核官时才能抢
|
||||||
|
jilu = ShenheJilu.objects.select_for_update().filter(
|
||||||
|
jilu_id=jilu_id,
|
||||||
|
zhuangtai=3,
|
||||||
|
shenheguan_id__isnull=True
|
||||||
|
).first()
|
||||||
|
if not jilu:
|
||||||
|
return Response({'code': 400, 'msg': '该审核任务已被接走、状态变更或不存在'})
|
||||||
|
|
||||||
|
# 4. 不能审核自己
|
||||||
|
if jilu.shenqingren_id == yonghu.yonghuid:
|
||||||
|
return Response({'code': 400, 'msg': '不能审核自己提交的申请'})
|
||||||
|
|
||||||
|
# 5. 板块权限:考核官必须属于该板块
|
||||||
|
is_related = KaoheguanBankuai.objects.filter(
|
||||||
|
kaoheguan_id=yonghu.yonghuid,
|
||||||
|
bankuai=jilu.bankuai
|
||||||
|
).exists()
|
||||||
|
if not is_related:
|
||||||
|
return Response({'code': 400, 'msg': '您不属于该板块,无法审核'})
|
||||||
|
|
||||||
|
# 新增:6. 费用>0时,要求考核官已认证
|
||||||
|
if jilu.jiaofei_jine and float(jilu.jiaofei_jine) > 0:
|
||||||
|
if not kg.is_renzheng:
|
||||||
|
return Response({'code': 400, 'msg': '本考核需要认证考核官接单,您尚未认证'})
|
||||||
|
|
||||||
|
# 7. 更新审核记录:分配给当前考核官,状态变为审核中
|
||||||
|
jilu.zhuangtai = 0 # 审核中
|
||||||
|
jilu.shenheguan_id = yonghu.yonghuid
|
||||||
|
jilu.save()
|
||||||
|
|
||||||
|
# 8. 更新考核官扩展表:审核总数 +1,审核状态置为审核中(1)
|
||||||
|
UserShenheguan.objects.filter(user=yonghu).update(
|
||||||
|
shenhe_zongshu=F('shenhe_zongshu') + 1,
|
||||||
|
shenhe_zhuangtai=1
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'code': 0,
|
||||||
|
'msg': '抢单成功,请前往游戏内审核,审核完成后请到考核打分页面进行评定。',
|
||||||
|
'data': {'bankuai_id': jilu.bankuai.bankuai_id}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"审核官抢单异常: {str(e)}", exc_info=True)
|
||||||
|
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db import transaction, models
|
||||||
|
from django.db.models import F, Q, OuterRef, Subquery, Count, Max
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
class ShenheXiangqingView(APIView):
|
||||||
|
"""获取当前考核官审核中的记录详情(新增待收考核费字段)"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||||||
|
if not kg:
|
||||||
|
return Response({'code': 403, 'msg': '您不是考核官或账号异常'})
|
||||||
|
|
||||||
|
page = int(request.data.get('page', 1))
|
||||||
|
page_size = int(request.data.get('page_size', 10))
|
||||||
|
|
||||||
|
# 查询当前考核官名下状态为“审核中”的记录
|
||||||
|
base_query = ShenheJilu.objects.filter(
|
||||||
|
shenheguan_id=yonghu.yonghuid,
|
||||||
|
zhuangtai=0
|
||||||
|
).select_related('bankuai', 'chenghao')
|
||||||
|
total = base_query.count()
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
jilu_list = base_query[offset:offset + page_size]
|
||||||
|
|
||||||
|
records = []
|
||||||
|
for jl in jilu_list:
|
||||||
|
user = UserMain.objects.filter(yonghuid=jl.shenqingren_id).first()
|
||||||
|
avatar = user.avatar if user else ''
|
||||||
|
texiao_json = {}
|
||||||
|
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||||||
|
try:
|
||||||
|
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 是否开启金牌
|
||||||
|
is_jinpai = jl.chenghao.kaioi_jinpai if jl.chenghao else False
|
||||||
|
|
||||||
|
# 本次待收考核费(基于记录中的当前次数,从费用明细表查询)
|
||||||
|
daishou_fei = 0.00
|
||||||
|
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||||||
|
jilu=jl,
|
||||||
|
cishu=jl.cishu
|
||||||
|
).first()
|
||||||
|
if fee_detail:
|
||||||
|
daishou_fei = float(fee_detail.jine)
|
||||||
|
else:
|
||||||
|
# 兜底:若明细表无记录,尝试用通用费用表计算
|
||||||
|
daishou_fei = 0.00
|
||||||
|
|
||||||
|
# 上次失败原因
|
||||||
|
last_fail_reason = ''
|
||||||
|
last_fail = KaoheJujueJilu.objects.filter(
|
||||||
|
jilu=jl,
|
||||||
|
cishu=jl.cishu - 1
|
||||||
|
).order_by('-create_time').first()
|
||||||
|
if last_fail:
|
||||||
|
last_fail_reason = last_fail.yuanyin
|
||||||
|
|
||||||
|
records.append({
|
||||||
|
'jilu_id': jl.jilu_id,
|
||||||
|
'shenqingren_id': jl.shenqingren_id,
|
||||||
|
'shenqingren_avatar': avatar,
|
||||||
|
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
|
||||||
|
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
|
||||||
|
'chenghao_texiao_json': texiao_json,
|
||||||
|
'guize_neirong': jl.guize_neirong or '',
|
||||||
|
'dashou_beizhu': jl.dashou_beizhu or '',
|
||||||
|
'dashou_youxi_id': jl.dashou_youxi_id or '',
|
||||||
|
'shenheguan_youxi_id': jl.shenheguan_youxi_id or '',
|
||||||
|
'cishu': jl.cishu,
|
||||||
|
'daishou_fei': daishou_fei, # 新增:待收考核费
|
||||||
|
'last_fail_reason': last_fail_reason,
|
||||||
|
'is_jinpai': is_jinpai,
|
||||||
|
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
|
||||||
|
})
|
||||||
|
|
||||||
|
has_more = (page * page_size) < total
|
||||||
|
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
class ShenheCaozuoView(APIView):
|
||||||
|
"""
|
||||||
|
考核官执行操作(同意/拒绝/转移/修改昵称),并更新考核官收益
|
||||||
|
- 只有“同意”或“拒绝”才增加余额和总额,金额严格从 KaoheJiluFeiyong 表获取
|
||||||
|
- 查不到费用则加 0 元,绝不影响流程
|
||||||
|
- 转移、修改昵称等操作不涉及收益
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
# 1. 验证考核官身份及账号状态
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||||||
|
if not kg:
|
||||||
|
return Response({'code': 403, 'msg': '账号异常,无法操作'})
|
||||||
|
|
||||||
|
jilu_id = request.data.get('jilu_id')
|
||||||
|
caozuo = request.data.get('caozuo') # agree / reject / transfer / modify_nick
|
||||||
|
if not jilu_id or not caozuo:
|
||||||
|
return Response({'code': 400, 'msg': '缺少必要参数'})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# 2. 锁定审核记录,确保属于当前考核官且状态为审核中(0)
|
||||||
|
jilu = ShenheJilu.objects.select_for_update().filter(
|
||||||
|
jilu_id=jilu_id,
|
||||||
|
shenheguan_id=yonghu.yonghuid,
|
||||||
|
zhuangtai=0
|
||||||
|
).first()
|
||||||
|
if not jilu:
|
||||||
|
return Response({'code': 400, 'msg': '该记录不存在或不属于您'})
|
||||||
|
|
||||||
|
# 3. 本次操作产生的收入(仅通过/拒绝时有意义)
|
||||||
|
daishou_fei = 0.00
|
||||||
|
|
||||||
|
# 4. 根据操作类型执行对应业务逻辑
|
||||||
|
if caozuo == 'agree':
|
||||||
|
# --- 通过考核 ---
|
||||||
|
# 查询本次考核应得的费用(基于记录ID和当前次数)
|
||||||
|
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||||||
|
jilu=jilu,
|
||||||
|
cishu=jilu.cishu
|
||||||
|
).first()
|
||||||
|
if fee_detail:
|
||||||
|
daishou_fei = float(fee_detail.jine)
|
||||||
|
# 找不到就保持 0,绝不使用任何默认值
|
||||||
|
|
||||||
|
jilu.zhuangtai = 1
|
||||||
|
jilu.save()
|
||||||
|
|
||||||
|
# 授予标签
|
||||||
|
if jilu.chenghao:
|
||||||
|
yh = UserMain.objects.get(yonghuid=jilu.shenqingren_id)
|
||||||
|
if not YonghuChenghao.objects.filter(yonghu=yh, chenghao=jilu.chenghao).exists():
|
||||||
|
YonghuChenghao.objects.create(yonghu=yh, chenghao=jilu.chenghao)
|
||||||
|
# 如果标签开启了金牌,同步更新打手称号
|
||||||
|
if jilu.chenghao.kaioi_jinpai:
|
||||||
|
dashou_prof = UserDashou.objects.filter(user=yh).first()
|
||||||
|
if dashou_prof:
|
||||||
|
dashou_prof.chenghao = '金牌选手'
|
||||||
|
dashou_prof.save()
|
||||||
|
|
||||||
|
elif caozuo == 'reject':
|
||||||
|
# --- 拒绝考核 ---
|
||||||
|
yuanyin = request.data.get('yuanyin', '')
|
||||||
|
if not yuanyin.strip():
|
||||||
|
return Response({'code': 400, 'msg': '请填写拒绝原因'})
|
||||||
|
|
||||||
|
# 查询本次考核应得的费用(拒绝时同样获得收入)
|
||||||
|
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||||||
|
jilu=jilu,
|
||||||
|
cishu=jilu.cishu
|
||||||
|
).first()
|
||||||
|
if fee_detail:
|
||||||
|
daishou_fei = float(fee_detail.jine)
|
||||||
|
# 找不到就保持 0
|
||||||
|
|
||||||
|
jilu.zhuangtai = 2
|
||||||
|
jilu.save()
|
||||||
|
# 记录拒绝原因
|
||||||
|
KaoheJujueJilu.objects.create(
|
||||||
|
jilu=jilu,
|
||||||
|
cishu=jilu.cishu,
|
||||||
|
shenheguan_id=yonghu.yonghuid,
|
||||||
|
yuanyin=yuanyin
|
||||||
|
)
|
||||||
|
|
||||||
|
# 新增:不合格时,如果考核官未认证,则清空考核官ID和游戏昵称
|
||||||
|
if not kg.is_renzheng:
|
||||||
|
jilu.shenheguan_id = None
|
||||||
|
jilu.shenheguan_youxi_id = ''
|
||||||
|
jilu.save(update_fields=['shenheguan_id', 'shenheguan_youxi_id'])
|
||||||
|
|
||||||
|
elif caozuo == 'transfer':
|
||||||
|
# 转移考核官:清空考核官信息,状态回退为待审核
|
||||||
|
jilu.shenheguan_id = None
|
||||||
|
jilu.shenheguan_youxi_id = None
|
||||||
|
jilu.zhuangtai = 3
|
||||||
|
jilu.save()
|
||||||
|
# 转移不更新收益,直接返回
|
||||||
|
|
||||||
|
|
||||||
|
elif caozuo == 'modify_nick':
|
||||||
|
# 修改考核官游戏昵称
|
||||||
|
youxi_id = request.data.get('youxi_id', '').strip()
|
||||||
|
if not youxi_id:
|
||||||
|
return Response({'code': 400, 'msg': '请输入游戏昵称'})
|
||||||
|
jilu.shenheguan_youxi_id = youxi_id
|
||||||
|
jilu.save()
|
||||||
|
return Response({'code': 0, 'msg': '修改成功'})
|
||||||
|
|
||||||
|
else:
|
||||||
|
return Response({'code': 400, 'msg': '无效的操作类型'})
|
||||||
|
|
||||||
|
# 5. 更新考核官收益(仅“通过”或“拒绝”时执行)
|
||||||
|
if caozuo in ('transfer'):
|
||||||
|
update_fields = {
|
||||||
|
|
||||||
|
'shenhe_zhuangtai': 0, # 设为空闲
|
||||||
|
}
|
||||||
|
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
|
||||||
|
if caozuo in ('agree', 'reject'):
|
||||||
|
update_fields = {
|
||||||
|
'yue': F('yue') + Decimal(str(daishou_fei)),
|
||||||
|
'zonge': F('zonge') + Decimal(str(daishou_fei)),
|
||||||
|
'shenhe_zhuangtai': 0, # 设为空闲
|
||||||
|
}
|
||||||
|
# 只有同意时才增加“通过总数”
|
||||||
|
if caozuo == 'agree':
|
||||||
|
update_fields['tongguo_zongshu'] = F('tongguo_zongshu') + 1
|
||||||
|
|
||||||
|
# 审核总数不再增加
|
||||||
|
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
|
||||||
|
|
||||||
|
return Response({'code': 0, 'msg': '操作成功'})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"考核操作异常: {str(e)}", exc_info=True)
|
||||||
|
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# dengji/views.py
|
||||||
|
class KaoheJinpaiTagsView(APIView):
|
||||||
|
"""获取考核金牌页面的标签和板块信息"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
user = request.user
|
||||||
|
# 获取用户已拥有的标签名称集合
|
||||||
|
owned_names = set(
|
||||||
|
YonghuChenghao.objects.filter(yonghu=user, chenghao__leixing='dashou')
|
||||||
|
.values_list('chenghao__mingcheng', flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 板块列表
|
||||||
|
plate_list = []
|
||||||
|
for bk in Bankuai.objects.all():
|
||||||
|
tags = Chenghao.objects.filter(bankuai=bk, leixing='dashou')
|
||||||
|
tag_list = []
|
||||||
|
for tag in tags:
|
||||||
|
# 费用列表
|
||||||
|
fee_objs = KaoheCishuFeiyong.objects.filter(chenghao=tag).order_by('cishu')
|
||||||
|
fee_list = [{'cishu': f.cishu, 'feiyong': float(f.feiyong)} for f in fee_objs]
|
||||||
|
|
||||||
|
# 若没有记录,补充默认费用
|
||||||
|
if not fee_list:
|
||||||
|
fee_list = [{'cishu': 1, 'feiyong': 5.00}]
|
||||||
|
|
||||||
|
tag_list.append({
|
||||||
|
'id': tag.id,
|
||||||
|
'name': tag.mingcheng,
|
||||||
|
'texiao_json': json.loads(tag.texiao_miaoshu) if tag.texiao_miaoshu else {},
|
||||||
|
'jinpai': tag.kaioi_jinpai,
|
||||||
|
'rule': tag.kaohe_guize or '',
|
||||||
|
'fee_list': fee_list,
|
||||||
|
'owned': tag.mingcheng in owned_names,
|
||||||
|
})
|
||||||
|
|
||||||
|
if tag_list:
|
||||||
|
plate_list.append({
|
||||||
|
'bankuai_id': bk.bankuai_id,
|
||||||
|
'mingcheng': bk.mingcheng,
|
||||||
|
'tags': tag_list
|
||||||
|
})
|
||||||
|
|
||||||
|
# 新增:返回所有在职考核官信息(用于指定考核官)
|
||||||
|
kaoheguan_list = []
|
||||||
|
for kg in UserShenheguan.objects.filter(zhuangtai=1).select_related('user'):
|
||||||
|
uid = kg.user.yonghuid
|
||||||
|
avatar = kg.user.avatar or ''
|
||||||
|
# 获取考核官的打手昵称(从打手扩展表)
|
||||||
|
dashou_nick = ''
|
||||||
|
dashou_prof = UserDashou.objects.filter(user=kg.user).first()
|
||||||
|
if dashou_prof:
|
||||||
|
dashou_nick = dashou_prof.nicheng or ''
|
||||||
|
kaoheguan_list.append({
|
||||||
|
'yonghuid': uid,
|
||||||
|
'avatar': avatar,
|
||||||
|
'dashou_nick': dashou_nick,
|
||||||
|
'is_renzheng': kg.is_renzheng,
|
||||||
|
'shenhe_zhuangtai': kg.shenhe_zhuangtai,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
'code': 0,
|
||||||
|
'data': {
|
||||||
|
'plate_list': plate_list,
|
||||||
|
'my_tags': list(owned_names),
|
||||||
|
'kaoheguan_list': kaoheguan_list, # 新增
|
||||||
|
}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
# dengji/utils.py
|
||||||
|
from .models import Chenghao, KaoheCishuFeiyong, ShenheJilu, KaoheJiluFeiyong, YonghuChenghao
|
||||||
|
from yonghu.models import UserMain
|
||||||
|
from django.db import models
|
||||||
|
class KaoheJiluView(APIView):
|
||||||
|
"""获取打手本人的考核记录(支持筛选状态)"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
user = request.user
|
||||||
|
status = request.data.get('status', 'pending') # pending / passed
|
||||||
|
page = int(request.data.get('page', 1))
|
||||||
|
page_size = int(request.data.get('page_size', 5))
|
||||||
|
|
||||||
|
if status == 'pending':
|
||||||
|
# 进行中/未通过:审核中(0)、待审核(3)、未通过(2)
|
||||||
|
query = ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai__in=[0, 2, 3]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
query = ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid,
|
||||||
|
zhuangtai=1 # 已通过
|
||||||
|
)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
records = query.order_by('-create_time')[(page-1)*page_size : page*page_size]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for jl in records:
|
||||||
|
# 汇总缴纳总额
|
||||||
|
total_fee = KaoheJiluFeiyong.objects.filter(jilu=jl).aggregate(
|
||||||
|
total=models.Sum('jine')
|
||||||
|
)['total'] or 0.00
|
||||||
|
|
||||||
|
# 上次拒绝原因(最近一条)
|
||||||
|
last_fail = KaoheJujueJilu.objects.filter(jilu=jl).order_by('-create_time').first()
|
||||||
|
fail_reason = last_fail.yuanyin if last_fail else ''
|
||||||
|
|
||||||
|
# 考核官头像
|
||||||
|
kg_user = UserMain.objects.filter(yonghuid=jl.shenheguan_id).first()
|
||||||
|
kg_avatar = kg_user.avatar if kg_user else ''
|
||||||
|
|
||||||
|
data.append({
|
||||||
|
'jilu_id': jl.jilu_id,
|
||||||
|
'chenghao_name': jl.chenghao.mingcheng if jl.chenghao else '',
|
||||||
|
'bankuai_name': jl.bankuai.mingcheng if jl.bankuai else '',
|
||||||
|
'zhuangtai': jl.zhuangtai,
|
||||||
|
'statusText': dict(ShenheJilu._meta.get_field('zhuangtai').choices).get(jl.zhuangtai, '未知'),
|
||||||
|
'statusColor': {0: '#FFA500', 1: '#27ae60', 2: '#e74c3c', 3: '#3498db'}.get(jl.zhuangtai, '#888'),
|
||||||
|
'shenheguan_id': jl.shenheguan_id,
|
||||||
|
'shenheguan_avatar': kg_avatar,
|
||||||
|
'dashou_youxi_id': jl.dashou_youxi_id,
|
||||||
|
'dashou_beizhu': jl.dashou_beizhu,
|
||||||
|
'cishu': jl.cishu,
|
||||||
|
'total_fee': float(total_fee),
|
||||||
|
'last_fail_reason': fail_reason,
|
||||||
|
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
# 为再次申请预留标签对象数据(前端可能用到)
|
||||||
|
'tag_obj': {
|
||||||
|
'id': jl.chenghao_id,
|
||||||
|
'name': jl.chenghao.mingcheng if jl.chenghao else '',
|
||||||
|
# 其他字段按需,但申请时会重新拉取
|
||||||
|
} if jl.chenghao else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
has_more = (page * page_size) < total
|
||||||
|
return Response({
|
||||||
|
'code': 0,
|
||||||
|
'data': {'list': data, 'has_more': has_more, 'total': total}
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
from .utils import validate_shenheguan
|
||||||
|
class KaoheFreeApplyView(APIView):
|
||||||
|
"""免费申请考核(首次/再次,费用为0时调用)"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
user = request.user
|
||||||
|
tag_id = request.data.get('tag_id')
|
||||||
|
game_nick = request.data.get('game_nick', '').strip()
|
||||||
|
remark = request.data.get('remark', '').strip()
|
||||||
|
reapply = request.data.get('reapply', False)
|
||||||
|
jilu_id = request.data.get('jilu_id') if reapply else None
|
||||||
|
shenheguan_id = request.data.get('shenheguan_id', '').strip() # 新增
|
||||||
|
|
||||||
|
if not tag_id or not game_nick:
|
||||||
|
return Response({'code': 400, 'msg': '标签ID和游戏昵称必填'})
|
||||||
|
|
||||||
|
# 校验费用是否为0
|
||||||
|
try:
|
||||||
|
chenghao = Chenghao.objects.get(id=tag_id)
|
||||||
|
except Chenghao.DoesNotExist:
|
||||||
|
return Response({'code': 400, 'msg': '标签不存在'})
|
||||||
|
|
||||||
|
# 计算本次次数
|
||||||
|
if reapply and jilu_id:
|
||||||
|
last_record = ShenheJilu.objects.filter(
|
||||||
|
jilu_id=jilu_id, shenqingren_id=user.yonghuid, zhuangtai=2
|
||||||
|
).first()
|
||||||
|
if not last_record:
|
||||||
|
return Response({'code': 400, 'msg': '原记录不存在或状态不是未通过'})
|
||||||
|
new_cishu = last_record.cishu + 1
|
||||||
|
else:
|
||||||
|
max_cishu = ShenheJilu.objects.filter(
|
||||||
|
shenqingren_id=user.yonghuid, chenghao=chenghao, zhuangtai__in=[1,2]
|
||||||
|
).aggregate(max=models.Max('cishu'))['max'] or 0
|
||||||
|
new_cishu = max_cishu + 1
|
||||||
|
|
||||||
|
fee = get_tag_fee(chenghao, new_cishu)
|
||||||
|
if fee > 0:
|
||||||
|
return Response({'code': 400, 'msg': '您有未通过的记录请查看并重考'})
|
||||||
|
|
||||||
|
# 新增:如果指定了考核官,进行合法性校验
|
||||||
|
if shenheguan_id:
|
||||||
|
valid, err = validate_shenheguan(shenheguan_id, chenghao.bankuai, fee, user)
|
||||||
|
if not valid:
|
||||||
|
return Response({'code': 400, 'msg': err})
|
||||||
|
|
||||||
|
# 调用公共创建逻辑
|
||||||
|
success, msg, record = create_shenhe_jilu(
|
||||||
|
user, tag_id, game_nick, remark, reapply, jilu_id,
|
||||||
|
shenheguan_id=shenheguan_id # 新增参数
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
return Response({'code': 0, 'msg': msg, 'data': {'jilu_id': record.jilu_id}})
|
||||||
|
else:
|
||||||
|
return Response({'code': 400, 'msg': msg})
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class KaoheJiluListView(APIView):
|
||||||
|
"""
|
||||||
|
获取当前考核官的历史审核记录
|
||||||
|
路径:POST /dengji/khghqkhjl
|
||||||
|
权限:JWT Token (考核官)
|
||||||
|
参数:
|
||||||
|
bankuai_id : int (可选,0或空表示全部)
|
||||||
|
status : int (可选,0-全部 1-审核中 2-已通过 3-未通过)
|
||||||
|
page : int (默认1)
|
||||||
|
page_size : int (默认5)
|
||||||
|
返回:
|
||||||
|
list, has_more, total
|
||||||
|
"""
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
try:
|
||||||
|
yonghu = request.user
|
||||||
|
# 1. 验证考核官身份及账号状态
|
||||||
|
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||||||
|
if not kg:
|
||||||
|
return Response({'code': 403, 'msg': '您不是考核官或账号异常'})
|
||||||
|
|
||||||
|
# 2. 获取参数
|
||||||
|
bankuai_id = request.data.get('bankuai_id')
|
||||||
|
status = int(request.data.get('status', 0)) # 0全部 1审核中 2已通过 3未通过
|
||||||
|
page = int(request.data.get('page', 1))
|
||||||
|
page_size = int(request.data.get('page_size', 5))
|
||||||
|
|
||||||
|
# 3. 构建查询条件:只查当前考核官审核的记录
|
||||||
|
base_query = ShenheJilu.objects.filter(shenheguan_id=yonghu.yonghuid)
|
||||||
|
|
||||||
|
# 板块筛选
|
||||||
|
if bankuai_id and int(bankuai_id) > 0:
|
||||||
|
base_query = base_query.filter(bankuai_id=int(bankuai_id))
|
||||||
|
|
||||||
|
# 状态筛选(前端传的状态映射到后端zhuangtai字段)
|
||||||
|
status_map = {
|
||||||
|
0: None, # 全部不筛选
|
||||||
|
1: 0, # 审核中
|
||||||
|
2: 1, # 已通过
|
||||||
|
3: 2, # 未通过
|
||||||
|
}
|
||||||
|
if status_map.get(status) is not None:
|
||||||
|
base_query = base_query.filter(zhuangtai=status_map[status])
|
||||||
|
|
||||||
|
# 4. 计数与分页
|
||||||
|
total = base_query.count()
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
jilu_list = base_query.order_by('-create_time')[offset:offset + page_size]
|
||||||
|
|
||||||
|
# 5. 预取关联数据,减少查询次数
|
||||||
|
jilu_list = jilu_list.select_related('bankuai', 'chenghao')
|
||||||
|
# 为每条记录预取费用明细和拒绝记录(通过 related_name 或手动过滤)
|
||||||
|
# 由于 KaoheJiluFeiyong 和 KaoheJujueJilu 通过外键指向 ShenheJilu (to_field='jilu_id'),
|
||||||
|
# 可以使用 prefetch_related
|
||||||
|
jilu_ids = [j.jilu_id for j in jilu_list]
|
||||||
|
fee_dict = {}
|
||||||
|
reject_dict = {}
|
||||||
|
if jilu_ids:
|
||||||
|
# 批量获取所有费用明细
|
||||||
|
fee_qs = KaoheJiluFeiyong.objects.filter(jilu_id__in=jilu_ids).order_by('cishu')
|
||||||
|
for fee in fee_qs:
|
||||||
|
if fee.jilu_id not in fee_dict:
|
||||||
|
fee_dict[fee.jilu_id] = []
|
||||||
|
fee_dict[fee.jilu_id].append({'cishu': fee.cishu, 'jine': float(fee.jine)})
|
||||||
|
|
||||||
|
# 批量获取所有拒绝记录
|
||||||
|
reject_qs = KaoheJujueJilu.objects.filter(jilu_id__in=jilu_ids).order_by('cishu')
|
||||||
|
for rej in reject_qs:
|
||||||
|
if rej.jilu_id not in reject_dict:
|
||||||
|
reject_dict[rej.jilu_id] = []
|
||||||
|
reject_dict[rej.jilu_id].append({
|
||||||
|
'cishu': rej.cishu,
|
||||||
|
'yuanyin': rej.yuanyin,
|
||||||
|
'create_time': rej.create_time.strftime('%Y-%m-%d %H:%M:%S') if rej.create_time else ''
|
||||||
|
})
|
||||||
|
|
||||||
|
# 6. 批量获取申请人信息(昵称和头像)
|
||||||
|
applicant_ids = {j.shenqingren_id for j in jilu_list}
|
||||||
|
user_map = {}
|
||||||
|
if applicant_ids:
|
||||||
|
users = UserMain.objects.filter(yonghuid__in=applicant_ids).select_related('dashou_profile')
|
||||||
|
for u in users:
|
||||||
|
avatar = u.avatar or ''
|
||||||
|
nicheng = ''
|
||||||
|
try:
|
||||||
|
# 打手扩展表可能有 nicheng
|
||||||
|
nicheng = u.dashou_profile.nicheng if u.dashou_profile else ''
|
||||||
|
except UserDashou.DoesNotExist:
|
||||||
|
pass
|
||||||
|
user_map[u.yonghuid] = {
|
||||||
|
'avatar': avatar,
|
||||||
|
'nicheng': nicheng or f'用户{u.yonghuid}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 7. 组装数据
|
||||||
|
records = []
|
||||||
|
for jl in jilu_list:
|
||||||
|
user_info = user_map.get(jl.shenqingren_id, {'avatar': '', 'nicheng': '未知'})
|
||||||
|
# 标签特效JSON
|
||||||
|
texiao_json = {}
|
||||||
|
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||||||
|
try:
|
||||||
|
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 汇总总缴纳费用
|
||||||
|
fee_items = fee_dict.get(jl.jilu_id, [])
|
||||||
|
total_fee = sum(item['jine'] for item in fee_items)
|
||||||
|
|
||||||
|
records.append({
|
||||||
|
'jilu_id': jl.jilu_id,
|
||||||
|
'shenqingren_id': jl.shenqingren_id,
|
||||||
|
'shenqingren_avatar': user_info['avatar'],
|
||||||
|
'shenqingren_nicheng': user_info['nicheng'],
|
||||||
|
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
|
||||||
|
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
|
||||||
|
'chenghao_texiao_json': texiao_json,
|
||||||
|
'zhuangtai': jl.zhuangtai, # 0审核中 1通过 2未通过 3待审核
|
||||||
|
'cishu': jl.cishu,
|
||||||
|
'total_fee': total_fee,
|
||||||
|
'dashou_youxi_id': jl.dashou_youxi_id or '',
|
||||||
|
'dashou_beizhu': jl.dashou_beizhu or '',
|
||||||
|
'shenheguan_youxi_id': jl.shenheguan_youxi_id or '',
|
||||||
|
'guize_neirong': jl.guize_neirong or '',
|
||||||
|
'create_time': jl.create_time.strftime('%Y-%m-%d %H:%M:%S') if jl.create_time else '',
|
||||||
|
'update_time': jl.update_time.strftime('%Y-%m-%d %H:%M:%S') if jl.update_time else '',
|
||||||
|
'fee_list': fee_items,
|
||||||
|
'reject_list': reject_dict.get(jl.jilu_id, []),
|
||||||
|
})
|
||||||
|
|
||||||
|
has_more = (page * page_size) < total
|
||||||
|
return Response({
|
||||||
|
'code': 0,
|
||||||
|
'data': {
|
||||||
|
'list': records,
|
||||||
|
'has_more': has_more,
|
||||||
|
'total': total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取考核记录异常: {str(e)}", exc_info=True)
|
||||||
|
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||||||
0
dingdan/__init__.py
Normal file
0
dingdan/__init__.py
Normal file
BIN
dingdan/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/admin.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/apps.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/models.cpython-312.pyc
Normal file
BIN
dingdan/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/models.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/signals.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/signals.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/tasks.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/tasks.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/urls.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/utils.cpython-312.pyc
Normal file
BIN
dingdan/__pycache__/utils.cpython-312.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/utils.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/utils.cpython-313.pyc
Normal file
Binary file not shown.
BIN
dingdan/__pycache__/views.cpython-313.pyc
Normal file
BIN
dingdan/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
3
dingdan/admin.py
Normal file
3
dingdan/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
10
dingdan/apps.py
Normal file
10
dingdan/apps.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DingdanConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'dingdan'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
# 导入信号处理,确保信号被注册
|
||||||
|
import dingdan.signals
|
||||||
43
dingdan/cross_platform_tasks.py
Normal file
43
dingdan/cross_platform_tasks.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# dingdan/cross_platform_tasks.py
|
||||||
|
|
||||||
|
#不在使用
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def send_partner_sync_order(partner_order_id, jine, dashou_fencheng, jieshao, beizhu, create_time,nicheng):
|
||||||
|
"""
|
||||||
|
异步发送派单通知给对方平台(使用简单线程,非Celery)
|
||||||
|
注意:此函数应在线程中调用,避免阻塞主请求。
|
||||||
|
生产环境建议使用Celery,此处提供线程兼容版本。
|
||||||
|
"""
|
||||||
|
url = f"{settings.PARTNER_BASE_URL}/dingdan/partner_sync_order"
|
||||||
|
payload = {
|
||||||
|
'token': settings.CROSS_PLATFORM_TOKEN,
|
||||||
|
'partner_order_id': partner_order_id,
|
||||||
|
'jine': jine,
|
||||||
|
'dashou_fencheng': dashou_fencheng,
|
||||||
|
'jieshao': jieshao,
|
||||||
|
'beizhu': beizhu,
|
||||||
|
'create_time': create_time,
|
||||||
|
'nicheng': nicheng,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=payload, timeout=5)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get('code') == 0:
|
||||||
|
logger.info(f"跨平台派单同步成功: {partner_order_id}")
|
||||||
|
# 可在此处保存对方返回的 local_order_id 到本地订单(如果需要)
|
||||||
|
# local_order_id = data['data'].get('local_order_id')
|
||||||
|
# Dingdan.objects.filter(dingdan_id=partner_order_id).update(partner_order_id=local_order_id)
|
||||||
|
else:
|
||||||
|
logger.error(f"跨平台派单同步业务失败: {data}")
|
||||||
|
else:
|
||||||
|
logger.error(f"跨平台派单同步HTTP失败: {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"跨平台派单同步异常: {str(e)}", exc_info=True)
|
||||||
186
dingdan/migrations/0001_initial.py
Normal file
186
dingdan/migrations/0001_initial.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-20 23:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Dingdan",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan_id",
|
||||||
|
models.CharField(max_length=32, unique=True, verbose_name="订单ID"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zhuangtai",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True, default=1, null=True, verbose_name="订单状态"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fadan_pingtai",
|
||||||
|
models.IntegerField(blank=True, null=True, verbose_name="发单平台"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jine",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="订单金额",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashou_fencheng",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="打手分成",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jiedan_dashou_id",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=32, null=True, verbose_name="接单打手ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zhiding_id",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=32, null=True, verbose_name="指定ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"shangpin_id",
|
||||||
|
models.IntegerField(blank=True, null=True, verbose_name="商品ID"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jieshao",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="订单介绍"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"beizhu",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="订单备注"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "订单",
|
||||||
|
"verbose_name_plural": "订单",
|
||||||
|
"db_table": "dingdan",
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["zhuangtai"], name="dingdan_zhuangt_2febe8_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["dingdan_id"], name="dingdan_dingdan_83dcb6_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["jiedan_dashou_id"], name="dingdan_jiedan__921bfa_idx"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DingdanPingtai",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"laoban_id",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=32, null=True, verbose_name="老板ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"laoban_pingjia",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="老板评价"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="pingtai_kuozhan",
|
||||||
|
to="dingdan.dingdan",
|
||||||
|
verbose_name="订单",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "平台订单扩展",
|
||||||
|
"verbose_name_plural": "平台订单扩展",
|
||||||
|
"db_table": "dingdan_pingtai",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DingdanShangjia",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"shangjia_id",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=32, null=True, verbose_name="商家ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"shangjia_pingjia",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="商家评价"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="shangjia_kuozhan",
|
||||||
|
to="dingdan.dingdan",
|
||||||
|
verbose_name="订单",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "商家订单扩展",
|
||||||
|
"verbose_name_plural": "商家订单扩展",
|
||||||
|
"db_table": "dingdan_shangjia",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-23 21:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="huiyuan_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, max_length=6, null=True, verbose_name="会员ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="yaoqiuleixing",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True, null=True, verbose_name="抢单要求类型"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="yongjin",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="佣金要求",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
55
dingdan/migrations/0003_lilubiao.py
Normal file
55
dingdan/migrations/0003_lilubiao.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-23 21:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0002_dingdan_huiyuan_id_dingdan_yaoqiuleixing_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Lilubiao",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"lilu",
|
||||||
|
models.DecimalField(
|
||||||
|
decimal_places=4,
|
||||||
|
max_digits=5,
|
||||||
|
null=True,
|
||||||
|
verbose_name="平台分成利率,1是平台,2是商家",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fadanpingtai",
|
||||||
|
models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="订单所属平台,(商家或平台)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "lilubiao",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
20
dingdan/migrations/0004_dingdan_nicheng.py
Normal file
20
dingdan/migrations/0004_dingdan_nicheng.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-24 23:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0003_lilubiao"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="nicheng",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, max_length=50, null=True, verbose_name="游戏昵称"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
dingdan/migrations/0005_dingdan_tupian.py
Normal file
20
dingdan/migrations/0005_dingdan_tupian.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-25 22:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0004_dingdan_nicheng"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="tupian",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, max_length=500, null=True, verbose_name="商品图片URL"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-26 17:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0005_dingdan_tupian"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="dashou_liuyan",
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name="打手留言"),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="leixing_id",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True, null=True, verbose_name="商品类型ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="dingdan_id",
|
||||||
|
field=models.CharField(
|
||||||
|
db_index=True, max_length=32, unique=True, verbose_name="订单ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="jiedan_dashou_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="接单打手ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="zhuangtai",
|
||||||
|
field=models.IntegerField(
|
||||||
|
blank=True, db_index=True, default=1, null=True, verbose_name="订单状态"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Dashoutupian",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan_id",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True, max_length=32, null=True, verbose_name="订单ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashou",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="接单打手ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tupian",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=500,
|
||||||
|
null=True,
|
||||||
|
verbose_name="打手提交图片URL",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "dashoutupian",
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["dashou"], name="dashoutupia_dashou_7de8bd_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["dingdan_id"], name="dashoutupia_dingdan_97c685_idx"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pingfen",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashou",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="接单打手ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zongfen",
|
||||||
|
models.PositiveIntegerField(default=0, verbose_name="总分"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"cishu",
|
||||||
|
models.PositiveIntegerField(default=0, verbose_name="评分次数"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "评分表",
|
||||||
|
"db_table": "pingfenbiao",
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["dashou"], name="pingfenbiao_dashou_f62f10_idx"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-30 15:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0006_dingdan_dashou_liuyan_dingdan_leixing_id_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Chufajilu",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashouid",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True,
|
||||||
|
max_length=7,
|
||||||
|
null=True,
|
||||||
|
verbose_name="被罚打手ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"qingqiuid",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True,
|
||||||
|
max_length=7,
|
||||||
|
null=True,
|
||||||
|
verbose_name="请求处罚ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"chuliid",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True,
|
||||||
|
max_length=7,
|
||||||
|
null=True,
|
||||||
|
verbose_name="处理处罚ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"cfliyou",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="处罚理由"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sqzhuangtai",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True,
|
||||||
|
default=0,
|
||||||
|
null=True,
|
||||||
|
verbose_name="处罚申请状态:0待处理,1成功,2驳回",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bhliyou",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="驳回处罚理由"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("jifen", models.IntegerField(default=0, verbose_name="扣除打手积分")),
|
||||||
|
(
|
||||||
|
"dingdan_id",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True, max_length=32, null=True, verbose_name="订单ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "chufajilu",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Tuikuanjilu",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan_id",
|
||||||
|
models.CharField(max_length=32, unique=True, verbose_name="订单ID"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashouid",
|
||||||
|
models.CharField(
|
||||||
|
max_length=7, null=True, verbose_name="接单打手ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"qingqiuid",
|
||||||
|
models.CharField(
|
||||||
|
max_length=7, null=True, verbose_name="请求用户ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"chuliid",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True, max_length=7, null=True, verbose_name="处理人ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"cfliyou",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="理由"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sqzhuangtai",
|
||||||
|
models.IntegerField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
default=0,
|
||||||
|
null=True,
|
||||||
|
verbose_name="处罚申请状态:0待处理,1成功,2驳回",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"bhliyou",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="驳回理由"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jine",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="订单金额",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dashou_fencheng",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="打手分成",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"jieshao",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="订单介绍"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"beizhu",
|
||||||
|
models.TextField(blank=True, null=True, verbose_name="订单备注"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"nicheng",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=50, null=True, verbose_name="游戏昵称"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "tuikuanjilu",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
name="bhliyou",
|
||||||
|
field=models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="驳回商家处罚理由"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
name="cfliyou",
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name="商家处罚理由"),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
name="sqzhuangtai",
|
||||||
|
field=models.IntegerField(
|
||||||
|
blank=True,
|
||||||
|
default=0,
|
||||||
|
null=True,
|
||||||
|
verbose_name="处罚申请状态:0待处理,1成功,2驳回",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dingdanpingtai",
|
||||||
|
name="laoban_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="老板ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
name="shangjia_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
max_length=32,
|
||||||
|
null=True,
|
||||||
|
verbose_name="商家ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="dingdanpingtai",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["laoban_id"], name="dingdan_pin_laoban__d55e6f_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["shangjia_id"], name="dingdan_sha_shangji_64d28b_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="chufajilu",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["dashouid"], name="chufajilu_dashoui_07aac0_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="chufajilu",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["qingqiuid"], name="chufajilu_qingqiu_ac160e_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="chufajilu",
|
||||||
|
index=models.Index(fields=["chuliid"], name="chufajilu_chuliid_cd63ac_idx"),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="chufajilu",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["dingdan_id"], name="chufajilu_dingdan_b8f0c2_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="tuikuanjilu",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["chuliid"], name="tuikuanjilu_chuliid_0b0f16_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="tuikuanjilu",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["sqzhuangtai"], name="tuikuanjilu_sqzhuan_d89dc9_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-30 17:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0007_chufajilu_tuikuanjilu_dingdanshangjia_bhliyou_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Ptliushui",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zonge",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="收取总金额",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zhichuzonge",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="支出总金额",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zongshouyi",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="总收益",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rizonge",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="日收取",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rizhichu",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="日支出",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rishouyi",
|
||||||
|
models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
verbose_name="日收益",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zongshoucishu",
|
||||||
|
models.IntegerField(default=0, verbose_name="总收取次数"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rishoucishu",
|
||||||
|
models.IntegerField(default=0, verbose_name="日收取次数"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zongzhichucishu",
|
||||||
|
models.IntegerField(default=0, verbose_name="总支出次数"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rizhichucishu",
|
||||||
|
models.IntegerField(default=0, verbose_name="日支出次数"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "ptliushui",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="tuikuanjilu",
|
||||||
|
old_name="cfliyou",
|
||||||
|
new_name="liyou",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="tuikuanjilu",
|
||||||
|
name="sqzhuangtai",
|
||||||
|
field=models.IntegerField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
default=0,
|
||||||
|
null=True,
|
||||||
|
verbose_name="申请状态:0待处理,1成功,2驳回",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
20
dingdan/migrations/0009_dingdan_liaotian_pindao.py
Normal file
20
dingdan/migrations/0009_dingdan_liaotian_pindao.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-31 11:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0008_ptliushui_rename_cfliyou_tuikuanjilu_liyou_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="liaotian_pindao",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, max_length=255, null=True, verbose_name="聊天频道名"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Generated by Django 6.0 on 2025-12-31 15:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0009_dingdan_liaotian_pindao"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="liaotian_pindao",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="user1_id",
|
||||||
|
field=models.CharField(
|
||||||
|
db_index=True, max_length=30, null=True, verbose_name="用户1标识"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="user2_id",
|
||||||
|
field=models.CharField(
|
||||||
|
db_index=True, max_length=30, null=True, verbose_name="用户2打手标识"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Liaotian",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
primary_key=True, serialize=False, verbose_name="主键ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user1_id",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True, max_length=30, verbose_name="用户1标识"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user2_id",
|
||||||
|
models.CharField(
|
||||||
|
db_index=True, max_length=30, verbose_name="用户2标识"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dingdan_id",
|
||||||
|
models.CharField(max_length=32, unique=True, verbose_name="订单ID"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zhuangtai",
|
||||||
|
models.IntegerField(
|
||||||
|
choices=[(1, "开启中"), (0, "已关闭")],
|
||||||
|
db_index=True,
|
||||||
|
default=1,
|
||||||
|
verbose_name="聊天状态",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"create_time",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="创建时间"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"update_time",
|
||||||
|
models.DateTimeField(auto_now=True, verbose_name="更新时间"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"db_table": "liaotian",
|
||||||
|
"indexes": [
|
||||||
|
models.Index(
|
||||||
|
fields=["user1_id"], name="liaotian_user1_i_6be6cd_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["user2_id"], name="liaotian_user2_i_9f61a4_idx"
|
||||||
|
),
|
||||||
|
models.Index(
|
||||||
|
fields=["zhuangtai"], name="liaotian_zhuangt_5e8be5_idx"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
dingdan/migrations/0011_dingdanshangjia_sjnicheng.py
Normal file
18
dingdan/migrations/0011_dingdanshangjia_sjnicheng.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0 on 2026-01-01 00:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0010_remove_dingdan_liaotian_pindao_dingdan_user1_id_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdanshangjia",
|
||||||
|
name="sjnicheng",
|
||||||
|
field=models.CharField(max_length=50, null=True, verbose_name="商家昵称"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 4.2.27 on 2026-01-20 15:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("dingdan", "0011_dingdanshangjia_sjnicheng"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="auto_expire_at",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="计划过期时间"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="auto_task_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, default="", max_length=255, verbose_name="延时任务ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="pending_dispatch",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=False, verbose_name="待调度标记"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dingdan",
|
||||||
|
name="status_8_time",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="状态变为8的时间"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user