第一次提交:小程序前端最新版本
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