This commit is contained in:
ddrwode
2026-03-04 14:11:12 +08:00
parent 6780f184c2
commit 3303a980f6
5 changed files with 453 additions and 5 deletions

View File

@@ -3,16 +3,25 @@
联系记录 API需要登录
- GET /api/contacts -> 查询联系记录(支持分页、搜索)
- GET /api/contacts/query -> 按电话号码或微信号查询(数据展示)
- GET /api/contacts/export -> 导出联系记录为 Excel 文件(返回下载链接)
- POST /api/contacts -> 创建联系记录
- GET /api/contacts/{id} -> 查询单条记录
- PUT /api/contacts/{id} -> 更新记录
- DELETE /api/contacts/{id} -> 删除记录
"""
import os
import uuid
from datetime import datetime
from django.db.models import Q
from django.conf import settings
from rest_framework import status
from rest_framework.decorators import api_view
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
from server.core.response import api_success, api_error
from server.models import ContactRecord
from server.serializers import ContactRecordSerializer
@@ -26,6 +35,8 @@ def contact_list(request):
page_size = int(request.query_params.get("page_size", 10))
reply_status = request.query_params.get("reply_status")
wechat = request.query_params.get("wechat_exchanged")
start_date = request.query_params.get("start_date")
end_date = request.query_params.get("end_date")
qs = ContactRecord.objects.all()
if search:
@@ -38,6 +49,10 @@ def contact_list(request):
qs = qs.filter(reply_status=reply_status)
if wechat is not None:
qs = qs.filter(wechat_exchanged=wechat.lower() in ("true", "1"))
if start_date:
qs = qs.filter(created_at__gte=start_date)
if end_date:
qs = qs.filter(created_at__lte=f"{end_date} 23:59:59")
total = qs.count()
start = (page - 1) * page_size
@@ -84,6 +99,105 @@ def contact_query(request):
})
@api_view(["GET"])
def contact_export(request):
"""
导出联系记录为 Excel 文件。
支持与列表接口相同的筛选参数search, reply_status, wechat_exchanged, start_date, end_date
返回下载链接而不是直接返回文件流
"""
search = request.query_params.get("search", "")
reply_status = request.query_params.get("reply_status")
wechat = request.query_params.get("wechat_exchanged")
start_date = request.query_params.get("start_date")
end_date = request.query_params.get("end_date")
# 构建查询
qs = ContactRecord.objects.all()
if search:
qs = qs.filter(
Q(name__icontains=search) |
Q(position__icontains=search) |
Q(contact__icontains=search)
)
if reply_status:
qs = qs.filter(reply_status=reply_status)
if wechat is not None:
qs = qs.filter(wechat_exchanged=wechat.lower() in ("true", "1"))
if start_date:
qs = qs.filter(created_at__gte=start_date)
if end_date:
qs = qs.filter(created_at__lte=f"{end_date} 23:59:59")
# 创建 Excel 工作簿
wb = Workbook()
ws = wb.active
ws.title = "联系记录"
# 设置表头样式
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_font = Font(bold=True, color="FFFFFF", size=11)
header_alignment = Alignment(horizontal="center", vertical="center")
# 定义表头
headers = ["ID", "姓名", "岗位", "联系方式", "回复状态", "是否交换微信", "Worker ID", "备注", "联系时间", "创建时间"]
ws.append(headers)
# 应用表头样式
for cell in ws[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = header_alignment
# 写入数据
for record in qs.order_by("-created_at"):
ws.append([
record.id,
record.name,
record.position,
record.contact,
record.reply_status,
"" if record.wechat_exchanged else "",
record.worker_id,
record.notes,
record.contacted_at.strftime("%Y-%m-%d %H:%M:%S") if record.contacted_at else "",
record.created_at.strftime("%Y-%m-%d %H:%M:%S"),
])
# 调整列宽
column_widths = [8, 12, 15, 18, 12, 15, 15, 30, 20, 20]
for i, width in enumerate(column_widths, start=1):
ws.column_dimensions[chr(64 + i)].width = width
# 设置数据行样式
for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
for cell in row:
cell.alignment = Alignment(vertical="center")
# 确保导出目录存在
export_dir = settings.EXPORT_ROOT
os.makedirs(export_dir, exist_ok=True)
# 生成唯一文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
unique_id = uuid.uuid4().hex[:8]
filename = f"contacts_export_{timestamp}_{unique_id}.xlsx"
filepath = os.path.join(export_dir, filename)
# 保存文件
wb.save(filepath)
# 生成下载链接
download_url = f"{settings.MEDIA_URL}exports/{filename}"
return api_success({
"download_url": download_url,
"filename": filename,
"total_records": qs.count(),
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
@api_view(["GET", "PUT", "DELETE"])
def contact_detail(request, pk):
try:

View File

@@ -83,3 +83,13 @@ LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_I18N = True
USE_TZ = False
# ─── 静态文件和媒体文件 ───
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# 导出文件目录
EXPORT_ROOT = os.path.join(MEDIA_ROOT, "exports")

View File

@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
"""
Django URL 路由配置。
"""
from django.urls import path
from django.urls import path, re_path
from django.conf import settings
from django.views.static import serve
from server.api import auth, accounts, tasks, workers, filters, scripts, contacts, stats, settings
from server.api import auth, accounts, tasks, workers, filters, scripts, contacts, stats, settings as api_settings
urlpatterns = [
# ─── 健康检查 ───
@@ -42,6 +43,7 @@ urlpatterns = [
# ─── 联系记录 ───
path("api/contacts", contacts.contact_list),
path("api/contacts/query", contacts.contact_query),
path("api/contacts/export", contacts.contact_export),
path("api/contacts/<int:pk>", contacts.contact_detail),
# ─── 统计分析 ───
@@ -49,6 +51,9 @@ urlpatterns = [
path("api/stats/daily", stats.stats_daily),
# ─── 系统配置 ───
path("api/settings", settings.settings_list),
path("api/settings/<str:key>", settings.settings_detail),
path("api/settings", api_settings.settings_list),
path("api/settings/<str:key>", api_settings.settings_detail),
# ─── 媒体文件服务 ───
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
]