- export_leads.dspy: generate xlsx with openpyxl, base64 return - export_content.dspy: generate xlsx for content list - admin.ui: add export section with download buttons - load_path.py: register export endpoints for RBAC - pyproject.toml: add openpyxl dependency
65 lines
2.6 KiB
Plaintext
65 lines
2.6 KiB
Plaintext
|
|
import io
|
|
import base64
|
|
from openpyxl import Workbook
|
|
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
|
|
|
config = getConfig('.')
|
|
DBPools(config.databases)
|
|
dbname = get_module_dbname('cms')
|
|
|
|
async with db.sqlorContext(dbname) as sor:
|
|
rows = await sor.R('cms_leads', {'sort': 'created_at desc'})
|
|
|
|
wb = Workbook()
|
|
ws = wb.active
|
|
ws.title = '商机线索'
|
|
|
|
# Headers
|
|
headers = ['姓名', '公司', '电话', '邮箱', '行业', '地区', '来源', '感兴趣产品', '状态', '留言', '创建时间']
|
|
header_font = Font(bold=True, color='FFFFFF')
|
|
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
|
|
thin_border = Border(
|
|
left=Side(style='thin'), right=Side(style='thin'),
|
|
top=Side(style='thin'), bottom=Side(style='thin')
|
|
)
|
|
|
|
for col, h in enumerate(headers, 1):
|
|
cell = ws.cell(row=1, column=col, value=h)
|
|
cell.font = header_font
|
|
cell.fill = header_fill
|
|
cell.alignment = Alignment(horizontal='center')
|
|
cell.border = thin_border
|
|
|
|
# Status mapping
|
|
status_map = {'new': '新建', 'contacted': '已联系', 'qualified': '已确认', 'converted': '已转化', 'closed': '已关闭'}
|
|
source_map = {'website': '官网', 'phone': '电话', 'referral': '推荐', 'ai_extract': 'AI抽取'}
|
|
|
|
# Data rows
|
|
for i, row in enumerate(rows, 2):
|
|
ws.cell(row=i, column=1, value=row.get('name', ''))
|
|
ws.cell(row=i, column=2, value=row.get('company', ''))
|
|
ws.cell(row=i, column=3, value=row.get('phone', ''))
|
|
ws.cell(row=i, column=4, value=row.get('email', ''))
|
|
ws.cell(row=i, column=5, value=row.get('industry', ''))
|
|
ws.cell(row=i, column=6, value=row.get('region', ''))
|
|
ws.cell(row=i, column=7, value=source_map.get(row.get('source', ''), row.get('source', '')))
|
|
ws.cell(row=i, column=8, value=row.get('interest_products', ''))
|
|
ws.cell(row=i, column=9, value=status_map.get(row.get('status', ''), row.get('status', '')))
|
|
ws.cell(row=i, column=10, value=row.get('message', ''))
|
|
ws.cell(row=i, column=11, value=str(row.get('created_at', ''))[:19])
|
|
for col in range(1, 12):
|
|
ws.cell(row=i, column=col).border = thin_border
|
|
|
|
# Auto-width
|
|
for col in range(1, 12):
|
|
ws.column_dimensions[chr(64 + col) if col <= 26 else 'A'].width = 15
|
|
|
|
# Save to buffer and encode
|
|
buf = io.BytesIO()
|
|
wb.save(buf)
|
|
b64 = base64.b64encode(buf.getvalue()).decode()
|
|
|
|
filename = f'shangji_leads_{curDateString()}.xlsx'
|
|
return {'status': 'ok', 'filename': filename, 'data': b64, 'total': len(rows)}
|