2026-03-06 14:14:50 +08:00

177 lines
5.5 KiB
TypeScript

import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { isDepartmentAdmin, isSystemAdmin } from '@/lib/department-permission'
import { getMaterialUploadStats } from '@/lib/admin-material-stats'
import { prisma } from '@/lib/prisma'
interface StatsSessionUser {
role?: string | null
managedDepartment?: string | null
department?: string | null
}
function toDateStart(dateString: string): Date {
return new Date(`${dateString}T00:00:00`)
}
function toDateEnd(dateString: string): Date {
return new Date(`${dateString}T23:59:59.999`)
}
function escapeCsvValue(value: unknown): string {
if (value === null || value === undefined) {
return ''
}
const stringValue = String(value)
if (/[",\n]/.test(stringValue)) {
return `"${stringValue.replace(/"/g, '""')}"`
}
return stringValue
}
function formatDateTime(value: Date): string {
return value.toLocaleString('sv-SE', {
timeZone: 'Asia/Shanghai',
hour12: false,
})
}
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions)
const sessionUser = session?.user as StatsSessionUser | undefined
const role = sessionUser?.role
const canViewAllDepartments = isSystemAdmin(role)
const canViewDepartmentScope = isDepartmentAdmin(role)
const canViewStats = canViewAllDepartments || canViewDepartmentScope
const scopeDepartmentName = canViewDepartmentScope
? String(sessionUser?.managedDepartment || sessionUser?.department || '').trim()
: null
if (!session?.user || !canViewStats) {
return NextResponse.json({ error: '无权限' }, { status: 403 })
}
if (canViewDepartmentScope && !scopeDepartmentName) {
return NextResponse.json({ error: '当前账号未绑定部门,无法导出统计数据' }, { status: 400 })
}
const { searchParams } = new URL(request.url)
const startDate = searchParams.get('startDate')
const endDate = searchParams.get('endDate')
const departmentKey = searchParams.get('departmentKey')
const stats = await getMaterialUploadStats({
startDate,
endDate,
departmentKey,
scopeDepartmentName,
})
const selectedUserIds = Array.from(new Set(stats.selectedUsers.map((user) => user.userId)))
if (selectedUserIds.length === 0) {
const lines = ['素材ID,上传时间,产品型号,素材类型,店铺,场景,平台,品类,作者,上传账号,上传姓名,上传部门,下载次数,点赞数']
const csvContent = `\uFEFF${lines.join('\n')}`
const rangeStart = startDate || 'all'
const rangeEnd = endDate || 'all'
const fileName = `material-details-${rangeStart}-${rangeEnd}.csv`
return new NextResponse(csvContent, {
status: 200,
headers: {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': `attachment; filename="${encodeURIComponent(fileName)}"`,
},
})
}
const createdAtFilter: { gte?: Date; lte?: Date } = {}
if (startDate) {
createdAtFilter.gte = toDateStart(startDate)
}
if (endDate) {
createdAtFilter.lte = toDateEnd(endDate)
}
const materials = await prisma.material.findMany({
where: {
deletedAt: null,
userId: { in: selectedUserIds },
...(createdAtFilter.gte || createdAtFilter.lte ? { createdAt: createdAtFilter } : {}),
},
orderBy: { createdAt: 'desc' },
select: {
id: true,
productModel: true,
materialType: true,
categoryName: true,
sceneName: true,
platformName: true,
productCategoryName: true,
authorName: true,
createdAt: true,
downloadCount: true,
likeCount: true,
user: {
select: {
username: true,
realName: true,
department: true,
departmentRef: {
select: {
name: true,
},
},
},
},
},
})
const lines: string[] = []
lines.push('素材ID,上传时间,产品型号,素材类型,店铺,场景,平台,品类,作者,上传账号,上传姓名,上传部门,下载次数,点赞数')
for (const material of materials) {
const departmentName = material.user.departmentRef?.name || material.user.department || '未分配部门'
lines.push(
[
material.id,
formatDateTime(material.createdAt),
material.productModel,
material.materialType,
material.categoryName,
material.sceneName,
material.platformName,
material.productCategoryName,
material.authorName,
material.user.username,
material.user.realName || '',
departmentName,
material.downloadCount,
material.likeCount,
]
.map(escapeCsvValue)
.join(',')
)
}
const csvContent = `\uFEFF${lines.join('\n')}`
const rangeStart = startDate || 'all'
const rangeEnd = endDate || 'all'
const fileName = `material-details-${rangeStart}-${rangeEnd}.csv`
return new NextResponse(csvContent, {
status: 200,
headers: {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': `attachment; filename="${encodeURIComponent(fileName)}"`,
},
})
} catch (error) {
console.error('[Admin Data Stats Export] GET error:', error)
return NextResponse.json({ error: '导出 CSV 失败' }, { status: 500 })
}
}