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

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

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

205 lines
6.7 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'
import { uploadToS3, deleteFromS3 } from '@/lib/s3'
import { processImage, getProcessedFileName } from '@/lib/image-processor'
export async function PUT(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session || (session.user as any).role !== 'ADMIN') {
return NextResponse.json(
{ error: '无权限' },
{ status: 403 }
)
}
const formData = await req.formData()
const normalizeInput = (value: FormDataEntryValue | null) => {
if (!value) return null
const trimmed = value.toString().trim()
return trimmed ? trimmed : null
}
const id = formData.get('id') as string
const productModel = formData.get('productModel') as string
const categoryName = formData.get('categoryName') as string
const sceneName = formData.get('sceneName') as string
const materialType = (formData.get('materialType') as string) || '3DMAX'
const renderer = (formData.get('renderer') as string) || 'CR'
const authorName = formData.get('authorName') as string
const previewImage = formData.get('previewImage') as File | null
const brandName = normalizeInput(formData.get('brandName'))
const seriesName = normalizeInput(formData.get('seriesName'))
const skuCode = normalizeInput(formData.get('skuCode'))
const materialTexture = normalizeInput(formData.get('materialTexture'))
const renderTime = normalizeInput(formData.get('renderTime'))
const imageResolution = normalizeInput(formData.get('imageResolution'))
const colorMode = normalizeInput(formData.get('colorMode'))
// 音频相关字段
const audioDuration = normalizeInput(formData.get('audioDuration'))
const audioCategory = normalizeInput(formData.get('audioCategory'))
const audioScene = normalizeInput(formData.get('audioScene'))
// Validation - 音频素材不需要 categoryName 和 sceneName
const isAudioMaterial = materialType === '音频'
if (!id || !productModel || !materialType || !authorName) {
return NextResponse.json(
{ error: '所有字段都是必填的' },
{ status: 400 }
)
}
// 非音频素材需要 categoryName 和 sceneName
if (!isAudioMaterial && (!categoryName || !sceneName)) {
return NextResponse.json(
{ error: '店铺和场景是必填的' },
{ status: 400 }
)
}
// Get existing material
const existingMaterial = await prisma.material.findUnique({
where: { id },
})
if (!existingMaterial) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
let previewUrl = existingMaterial.previewUrl
// If new preview image is uploaded
if (previewImage && previewImage.size > 0) {
// Delete old preview image from S3
try {
await deleteFromS3(existingMaterial.previewUrl)
} catch (error) {
console.error('Error deleting old preview:', error)
}
// 处理预览图:压缩到300KB以内,转换为webP,缩放到600px
const originalBuffer = Buffer.from(await previewImage.arrayBuffer())
const processedBuffer = await processImage(originalBuffer, {
maxSizeKB: 300,
maxWidth: 600,
format: 'webp',
})
// Upload processed preview image to S3
const processedFileName = getProcessedFileName(previewImage.name)
const previewFileName = `preview-${Date.now()}-${processedFileName}`
previewUrl = await uploadToS3(
processedBuffer,
previewFileName,
'image/webp'
)
}
const userId = (session.user as any).id
// 构建更新数据,根据素材类型决定哪些字段需要更新
const updateData: any = {
previewUrl,
productModel,
brandName,
seriesName,
skuCode,
materialTexture,
materialType,
authorName,
}
// 非音频素材更新这些字段
if (!isAudioMaterial) {
updateData.categoryName = categoryName
updateData.sceneName = sceneName
updateData.renderer = renderer
updateData.renderTime = renderTime
updateData.imageResolution = imageResolution
updateData.colorMode = colorMode
} else {
// 音频素材更新音频相关字段
updateData.audioDuration = audioDuration
updateData.audioCategory = audioCategory
updateData.audioScene = audioScene
}
// Update material record
const material = await prisma.material.update({
where: { id },
data: updateData,
include: {
user: {
select: {
username: true,
},
},
},
})
// 记录变更日志
const changedFields: Record<string, { oldValue: any; newValue: any }> = {}
const collectChange = (key: string, newValue: any) => {
const oldValue = (existingMaterial as any)[key]
const normOld = oldValue ?? null
const normNew = newValue ?? null
if (normOld === null && normNew === null) return
if (String(normOld) !== String(normNew)) {
changedFields[key] = { oldValue: normOld, newValue: normNew }
}
}
collectChange('previewUrl', previewUrl)
collectChange('productModel', productModel)
collectChange('brandName', brandName)
collectChange('seriesName', seriesName)
collectChange('skuCode', skuCode)
collectChange('materialTexture', materialTexture)
collectChange('categoryName', categoryName)
collectChange('sceneName', sceneName)
collectChange('materialType', materialType)
collectChange('renderer', renderer)
collectChange('renderTime', renderTime)
collectChange('imageResolution', imageResolution)
collectChange('colorMode', colorMode)
collectChange('authorName', authorName)
// 音频相关字段
collectChange('audioDuration', audioDuration)
collectChange('audioCategory', audioCategory)
collectChange('audioScene', audioScene)
if (Object.keys(changedFields).length > 0) {
await createMaterialLog({
materialId: id,
userId,
action: 'UPDATE',
changes: changedFields,
})
}
// 转换BigInt为字符串
const materialData = {
...material,
zipFileSize: material.zipFileSize.toString()
}
return NextResponse.json(
{ message: '更新成功', material: materialData },
{ status: 200 }
)
} catch (error) {
console.error('Update error:', error)
return NextResponse.json(
{ error: '更新失败,请稍后再试' },
{ status: 500 }
)
}
}