402 lines
12 KiB
TypeScript
402 lines
12 KiB
TypeScript
'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()
|
||
|
||
// 权限检查
|
||
if (data.userId !== (session?.user as any)?.id && (session?.user as any)?.role !== 'ADMIN') {
|
||
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)
|
||
setError(error.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>
|
||
)
|
||
}
|
||
|