420 lines
13 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.

'use client'
import { useEffect, useState } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import Header from '@/components/Header'
import { requiresCategoryAndScene, requiresRenderer, requiresProductCategoryName } from '@/lib/material-validation'
interface Material {
id: string
userId: string
productModel: string
categoryName: string
sceneName: string
platformName?: string | null
productCategoryName?: string | null
materialType: string
renderer: string
authorName: string
}
export default function EditMaterialPage({ params }: { params: { id: string } }) {
const { data: session, status } = useSession()
const router = useRouter()
const [loading, setLoading] = useState(true)
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const [material, setMaterial] = useState<Material | null>(null)
const [productModel, setProductModel] = useState('')
const [categoryName, setCategoryName] = useState('')
const [sceneName, setSceneName] = useState('')
const [platformName, setPlatformName] = useState('')
const [productCategoryName, setProductCategoryName] = useState('')
const [materialType, setMaterialType] = useState('')
const [renderer, setRenderer] = useState('')
const [authorName, setAuthorName] = useState('')
const [categories, setCategories] = useState<Array<{ id: string; name: string }>>([])
const [scenes, setScenes] = useState<Array<{ id: string; name: string }>>([])
const [platforms, setPlatforms] = useState<Array<{ id: string; name: string; count: number }>>([])
const [productCategories, setProductCategories] = useState<Array<{ id: string; name: string; count: number }>>([])
const materialTypes = [
'3D场景',
'详情页',
'详情模板',
'材质',
'模特',
'实拍图片',
'买家秀图片',
'小红书种草',
'主图模版',
'SKU模版',
'主图视频',
'安装视频',
'种草视频',
'音乐',
'C4D模型',
'标准饰品',
'标准模型',
'品牌VI灵感',
'矢量文件'
]
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/login')
return
}
if (status === 'authenticated') {
fetchMaterial()
fetchCategories()
fetchScenes()
fetchPlatforms()
fetchProductCategories()
}
}, [status, params.id])
const fetchMaterial = async () => {
try {
const response = await fetch(`/api/materials/${params.id}`)
if (!response.ok) {
throw new Error('获取素材信息失败')
}
const data = await response.json()
// 权限检查:素材所有者、普通管理员或超级管理员都可以编辑
const userId = (session?.user as any)?.id
const userRole = (session?.user as any)?.role
// 检查是否是超级管理员
let isUserSuperAdmin = false
try {
const superAdminCheck = await fetch('/api/admin/check-super-admin')
if (superAdminCheck.ok) {
const superAdminData = await superAdminCheck.json()
isUserSuperAdmin = superAdminData.isSuperAdmin || false
}
} catch (e) {
console.warn('检查超级管理员状态失败:', e)
}
if (data.userId !== userId && userRole !== 'ADMIN' && !isUserSuperAdmin) {
setError('您没有权限编辑此素材')
setTimeout(() => router.push('/'), 2000)
return
}
setMaterial(data)
setProductModel(data.productModel || '')
setCategoryName(data.categoryName || '')
setSceneName(data.sceneName || '')
setPlatformName(data.platformName || '')
setProductCategoryName(data.productCategoryName || '')
setMaterialType(data.materialType || '')
setRenderer(data.renderer || '')
setAuthorName(data.authorName || '')
setLoading(false)
} catch (error: any) {
console.error('获取素材失败:', error)
const { parseApiError } = await import('@/lib/error-handler')
const { message } = parseApiError(error)
setError(message)
setLoading(false)
}
}
const fetchCategories = async () => {
try {
const response = await fetch('/api/categories')
if (response.ok) {
const data = await response.json()
setCategories(data)
}
} catch (error) {
console.error('获取分类失败:', error)
}
}
const fetchScenes = async () => {
try {
const response = await fetch('/api/scenes')
if (response.ok) {
const data = await response.json()
setScenes(data)
}
} catch (error) {
console.error('获取场景失败:', error)
}
}
const fetchPlatforms = async () => {
try {
const response = await fetch('/api/platforms')
if (response.ok) {
const data = await response.json()
setPlatforms(data)
}
} catch (error) {
console.error('获取平台分类失败:', error)
}
}
const fetchProductCategories = async () => {
try {
const response = await fetch('/api/product-categories')
if (response.ok) {
const data = await response.json()
setProductCategories(data)
}
} catch (error) {
console.error('获取品类分类失败:', error)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
setSuccess('')
// 基础必填字段验证
if (!productModel || !materialType || !authorName) {
setError('请填写所有必填字段')
return
}
// 使用统一的验证逻辑
if (requiresCategoryAndScene(materialType) && (!categoryName || !sceneName)) {
setError('请填写所有必填字段(店铺、场景)')
return
}
if (requiresProductCategoryName(materialType) && !productCategoryName) {
setError('标准模型类型必须选择品类')
return
}
if (requiresRenderer(materialType) && !renderer) {
setError('3D场景类型必须指定渲染器')
return
}
setSubmitting(true)
try {
const response = await fetch(`/api/materials/${params.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
productModel,
categoryName,
sceneName,
platformName: platformName || null,
productCategoryName: productCategoryName || null,
materialType,
renderer,
authorName
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || '更新失败')
}
setSuccess('素材信息更新成功!')
setTimeout(() => {
router.push('/')
}, 1500)
} catch (error: any) {
console.error('更新失败:', error)
setError(error.message || '更新失败,请稍后重试')
} finally {
setSubmitting(false)
}
}
if (status === 'loading' || loading) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<Header session={session} />
<div className="max-w-2xl mx-auto px-4 py-8">
<div className="text-center text-gray-600 dark:text-gray-400">加载中...</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<Header session={session} />
<div className="max-w-2xl mx-auto px-4 py-8">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-8">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">编辑素材信息</h1>
{error && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-600 rounded-lg">
{error}
</div>
)}
{success && (
<div className="mb-4 p-4 bg-green-50 border border-green-200 text-green-600 rounded-lg">
{success}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
产品型号 *
</label>
<input
type="text"
value={productModel}
onChange={(e) => setProductModel(e.target.value)}
className="input-apple"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
分类 *
</label>
<select
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
className="input-apple"
required
>
<option value="">请选择分类</option>
{categories.map((cat) => (
<option key={cat.id} value={cat.name}>{cat.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
场景 *
</label>
<select
value={sceneName}
onChange={(e) => setSceneName(e.target.value)}
className="input-apple"
required
>
<option value="">请选择场景</option>
{scenes.map((scene) => (
<option key={scene.id} value={scene.name}>{scene.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
平台分类(可选)
</label>
<select
value={platformName}
onChange={(e) => setPlatformName(e.target.value)}
className="input-apple"
>
<option value="">不选择</option>
{platforms.map((platform) => (
<option key={platform.id} value={platform.name}>{platform.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
品类分类(可选)
</label>
<select
value={productCategoryName}
onChange={(e) => setProductCategoryName(e.target.value)}
className="input-apple"
>
<option value="">不选择</option>
{productCategories.map((category) => (
<option key={category.id} value={category.name}>{category.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
素材类型 *
</label>
<select
value={materialType}
onChange={(e) => setMaterialType(e.target.value)}
className="input-apple"
required
>
<option value="">请选择素材类型</option>
{materialTypes.map((type) => (
<option key={type} value={type}>{type}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
渲染器 *
</label>
<input
type="text"
value={renderer}
onChange={(e) => setRenderer(e.target.value)}
className="input-apple"
placeholder="例如: V-Ray, Corona, Arnold"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
作者 *
</label>
<input
type="text"
value={authorName}
onChange={(e) => setAuthorName(e.target.value)}
className="input-apple"
required
/>
</div>
<div className="flex gap-4 pt-4">
<button
type="button"
onClick={() => router.back()}
className="flex-1 px-6 py-3 border border-gray-300 dark:border-gray-600 rounded-apple font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
disabled={submitting}
>
取消
</button>
<button
type="submit"
className="flex-1 btn-primary"
disabled={submitting}
>
{submitting ? '保存中...' : '保存修改'}
</button>
</div>
</form>
</div>
</div>
</div>
)
}