WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

317 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { createMaterialLog } from '@/lib/material-log'
// GET /api/materials/[id] - 获取单个素材详情
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const material = await prisma.material.findUnique({
where: { id: params.id },
select: {
id: true,
userId: true,
productModel: true,
materialKind: true,
brandName: true,
seriesName: true,
skuCode: true,
materialTexture: true,
categoryName: true,
sceneName: true,
platformName: true,
materialType: true,
renderer: true,
renderTime: true,
imageResolution: true,
colorMode: true,
authorName: true,
previewUrl: true,
videoCoverUrl: true,
videoPreview540Url: true,
videoPreviewClipUrl: true,
videoProcessingStatus: true,
videoDuration: true,
zipFileName: true,
zipFileSize: true,
downloadCount: true,
likeCount: true,
displayImages: true,
createdAt: true,
templateId: true,
// 音频相关字段
// @ts-ignore
audioDuration: true,
// @ts-ignore
audioCategory: true,
// @ts-ignore
audioScene: true,
// @ts-ignore
audioUrl: true,
user: {
select: {
id: true,
username: true,
avatar: true,
}
}
}
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 返回素材信息(转换BigInt为字符串)
return NextResponse.json({
id: material.id,
userId: material.userId,
productModel: material.productModel,
brandName: material.brandName,
seriesName: material.seriesName,
skuCode: material.skuCode,
materialTexture: material.materialTexture,
categoryName: material.categoryName,
sceneName: material.sceneName,
platformName: material.platformName,
materialType: material.materialType,
renderer: material.renderer,
renderTime: material.renderTime,
imageResolution: material.imageResolution,
colorMode: material.colorMode,
authorName: material.authorName,
previewUrl: material.previewUrl,
materialKind:
(material.materialKind || '').trim().toUpperCase() ||
(material.materialType === '视频' ? 'VIDEO' : material.materialType === '音频' ? 'AUDIO' : 'IMAGE'),
videoCoverUrl: material.videoCoverUrl,
videoPreview540Url: material.videoPreview540Url,
videoPreviewClipUrl: material.videoPreviewClipUrl,
videoProcessingStatus: material.videoProcessingStatus,
videoDuration: material.videoDuration,
zipFileName: material.zipFileName,
zipFileSize: material.zipFileSize.toString(),
downloadCount: material.downloadCount,
likeCount: material.likeCount,
displayImages: material.displayImages,
createdAt: material.createdAt,
templateId: material.templateId,
// 音频相关字段
audioDuration: (material as any).audioDuration,
audioCategory: (material as any).audioCategory,
audioScene: (material as any).audioScene,
audioUrl: (material as any).audioUrl,
user: {
username: material.user.username,
avatar: material.user.avatar
}
})
} catch (error: any) {
console.error('[Materials] GET [id] error:', error)
return NextResponse.json(
{ error: '获取素材失败' },
{ status: 500 }
)
}
}
// PUT /api/materials/[id] - 更新素材信息
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const session = await getServerSession(authOptions)
if (!session?.user) {
return NextResponse.json(
{ error: '请先登录' },
{ status: 401 }
)
}
const body = await request.json()
const {
productModel,
categoryName,
sceneName,
materialType,
renderer,
authorName,
brandName,
seriesName,
skuCode,
materialTexture,
renderTime,
imageResolution,
colorMode
} = body
// 验证必填字段
if (!productModel || !categoryName || !sceneName || !materialType || !renderer || !authorName) {
return NextResponse.json(
{ error: '缺少必填字段' },
{ status: 400 }
)
}
// 获取素材信息
const material = await prisma.material.findUnique({
where: { id: params.id }
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 权限检查:只有素材上传者或管理员可以编辑
const userFromSession = session.user as any
if (material.userId !== userFromSession.id && userFromSession.role !== 'ADMIN') {
return NextResponse.json(
{ error: '您没有权限编辑此素材' },
{ status: 403 }
)
}
// 记录修改前的值,用于日志
const oldValues: Record<string, any> = {
productModel: material.productModel,
categoryName: material.categoryName,
sceneName: material.sceneName,
materialType: material.materialType,
renderer: material.renderer,
authorName: material.authorName,
brandName: material.brandName,
seriesName: material.seriesName,
skuCode: material.skuCode,
materialTexture: material.materialTexture,
renderTime: material.renderTime,
imageResolution: material.imageResolution,
colorMode: material.colorMode,
}
// 更新素材信息
const updateData: any = {
productModel,
categoryName,
sceneName,
materialType,
renderer,
authorName,
updatedAt: new Date()
}
const assignOptional = (key: string, value: any) => {
if (value !== undefined) {
updateData[key] = value === null || value === '' ? null : value
}
}
assignOptional('brandName', brandName)
assignOptional('seriesName', seriesName)
assignOptional('skuCode', skuCode)
assignOptional('materialTexture', materialTexture)
assignOptional('renderTime', renderTime)
assignOptional('imageResolution', imageResolution)
assignOptional('colorMode', colorMode)
const updatedMaterial = await prisma.material.update({
where: { id: params.id },
data: updateData
})
// 记录修改详情到日志(用于监控仪表板)
const changes: string[] = []
const fieldNames: Record<string, string> = {
productModel: '产品型号',
categoryName: '一级分类',
sceneName: '二级分类',
materialType: '素材类型',
renderer: '渲染器',
authorName: '作者花名',
brandName: '品牌名称',
seriesName: '系列/产品线',
skuCode: 'SKU编码',
materialTexture: '材质/工艺',
renderTime: '渲染耗时',
imageResolution: '输出分辨率',
colorMode: '颜色模式',
}
Object.keys(updateData).forEach((key) => {
if (key === 'updatedAt') return
const oldVal = oldValues[key] ?? null
const newVal = updateData[key] ?? null
if (String(oldVal) !== String(newVal)) {
const fieldName = fieldNames[key] || key
changes.push(`${fieldName}: "${oldVal ?? '(空)'}" → "${newVal ?? '(空)'}"`)
}
})
if (changes.length > 0) {
console.log(`[MaterialUpdate] Material ${params.id} updated by ${userFromSession.id}:`, changes.join('; '))
// 记录详细修改日志到数据库
const changesObj: Record<string, { oldValue: any; newValue: any }> = {}
Object.keys(updateData).forEach((key) => {
if (key === 'updatedAt') return
const oldVal = oldValues[key] ?? null
const newVal = updateData[key] ?? null
if (String(oldVal) !== String(newVal)) {
changesObj[key] = {
oldValue: oldVal ?? null,
newValue: newVal ?? null,
}
}
})
if (Object.keys(changesObj).length > 0) {
await createMaterialLog({
materialId: params.id,
userId: userFromSession.id,
action: 'UPDATE',
changes: changesObj,
})
}
}
return NextResponse.json({
success: true,
message: '素材信息更新成功',
material: {
id: updatedMaterial.id,
productModel: updatedMaterial.productModel,
brandName: updatedMaterial.brandName,
seriesName: updatedMaterial.seriesName,
skuCode: updatedMaterial.skuCode,
materialTexture: updatedMaterial.materialTexture,
categoryName: updatedMaterial.categoryName,
sceneName: updatedMaterial.sceneName,
materialType: updatedMaterial.materialType,
renderer: updatedMaterial.renderer,
renderTime: updatedMaterial.renderTime,
imageResolution: updatedMaterial.imageResolution,
colorMode: updatedMaterial.colorMode,
authorName: updatedMaterial.authorName
}
})
} catch (error: any) {
console.error('[Materials] PUT [id] error:', error)
return NextResponse.json(
{ error: '更新素材失败' },
{ status: 500 }
)
}
}