1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
205 lines
6.7 KiB
TypeScript
205 lines
6.7 KiB
TypeScript
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 }
|
||
)
|
||
}
|
||
}
|
||
|