feat: 修复百科添加页面的问题
This commit is contained in:
+5
-1
@@ -22,6 +22,10 @@ export interface UpdateLevelParams extends Partial<CreateLevelParams> {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ListResponse {
|
||||||
|
list: LevelConf[];
|
||||||
|
}
|
||||||
|
|
||||||
// 获取等级配置列表 (虽然是 list 接口,但根据以往经验,可能返回带分页的结构或者直接是数组,先假设是 PageResult 或者是 List 结构,Swagger 上没细说。通常 /list 可能是分页的。如果 swagger 没写分页参数,那可能是全量列表)
|
// 获取等级配置列表 (虽然是 list 接口,但根据以往经验,可能返回带分页的结构或者直接是数组,先假设是 PageResult 或者是 List 结构,Swagger 上没细说。通常 /list 可能是分页的。如果 swagger 没写分页参数,那可能是全量列表)
|
||||||
// 根据 swagger "/config/level/list" 没有 parameters,大概率是全量列表。
|
// 根据 swagger "/config/level/list" 没有 parameters,大概率是全量列表。
|
||||||
// 假设返回结构 { data: LevelConf[], msg: string, code: number }
|
// 假设返回结构 { data: LevelConf[], msg: string, code: number }
|
||||||
@@ -43,7 +47,7 @@ export function getLevelConfDetail(id: string) {
|
|||||||
|
|
||||||
// 获取等级配置列表
|
// 获取等级配置列表
|
||||||
export function getLevelConfList() {
|
export function getLevelConfList() {
|
||||||
return get<{ data: LevelConf[] }>('/config/level/list')
|
return get<{ data: ListResponse }>('/config/level/list')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除等级配置 (Swagger 里没看到 delete 接口,先不加)
|
// 删除等级配置 (Swagger 里没看到 delete 接口,先不加)
|
||||||
|
|||||||
@@ -159,7 +159,18 @@ export function addWiki(data: CreateWikiParams) {
|
|||||||
return post<{ msg: string }>('/wiki/add', data)
|
return post<{ msg: string }>('/wiki/add', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 修改百科
|
// 修改百科
|
||||||
export function updateWiki(data: UpdateWikiParams) {
|
export function updateWiki(data: UpdateWikiParams) {
|
||||||
return post<{ msg: string }>('/wiki/update', data)
|
return post<{ msg: string }>('/wiki/update', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传百科图片 (列表页快捷上传)
|
||||||
|
export function uploadWikiImg(data: { id: string; ossIds: string[] }) {
|
||||||
|
return post<{ msg: string }>('/wiki/uploadImg', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除百科
|
||||||
|
export function deleteWiki(ids: string[]) {
|
||||||
|
return post<{ msg: string }>('/wiki/delete', { ids })
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ export default function TopicsPage() {
|
|||||||
const list = res.data?.list || []
|
const list = res.data?.list || []
|
||||||
|
|
||||||
// Map backend snake_case to camelCase if necessary (for robustness)
|
// Map backend snake_case to camelCase if necessary (for robustness)
|
||||||
const mappedList = list.map((item: any) => ({
|
const mappedList = list.map((item: Topic) => ({
|
||||||
...item,
|
...item,
|
||||||
startTime: (item.startTime || item.start_time) ? String(item.startTime || item.start_time) : undefined,
|
startTime: (item.startTime) ? String(item.startTime) : undefined,
|
||||||
endTime: (item.endTime || item.end_time) ? String(item.endTime || item.end_time) : undefined
|
endTime: (item.endTime) ? String(item.endTime) : undefined
|
||||||
})) as Topic[]
|
})) as Topic[]
|
||||||
|
|
||||||
setTopics(Array.isArray(mappedList) ? mappedList : [])
|
setTopics(Array.isArray(mappedList) ? mappedList : [])
|
||||||
@@ -108,15 +108,15 @@ export default function TopicsPage() {
|
|||||||
const handleEdit = async (topic: Topic) => {
|
const handleEdit = async (topic: Topic) => {
|
||||||
try {
|
try {
|
||||||
const res = await getTopicDetail(topic.id)
|
const res = await getTopicDetail(topic.id)
|
||||||
const detail = res.data as any // Cast to any to handle potential snake_case from backend
|
const detail = res.data as Topic // Cast to any to handle potential snake_case from backend
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
id: detail.id,
|
id: detail.id,
|
||||||
title: detail.title || '',
|
title: detail.title || '',
|
||||||
remark: detail.remark || '',
|
remark: detail.remark || '',
|
||||||
// backend might return start_time, map to startTime
|
// backend might return start_time, map to startTime
|
||||||
startTime: detail.startTime || detail.start_time || '',
|
startTime: detail.startTime || '',
|
||||||
endTime: detail.endTime || detail.end_time || '',
|
endTime: detail.endTime || '',
|
||||||
})
|
})
|
||||||
setIsEdit(true)
|
setIsEdit(true)
|
||||||
setDialogOpen(true)
|
setDialogOpen(true)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { Award, Trophy, Medal, Grid, Plus, Edit2, Trash2, Eye, Upload, X, Crown, Sprout, Star, Sparkles, Search, ChevronRight, ChevronDown, Layers, Hash } from 'lucide-react'
|
import { Award, Trophy, Medal, Grid, Plus, Edit2, Trash2, Eye, Upload, X, Sprout, Star, ChevronRight, ChevronDown, Layers, Hash } from 'lucide-react'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -214,7 +214,7 @@ export default function BadgeConfigPage() {
|
|||||||
|
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const { icon, ...payload } = formData
|
const { ...payload } = formData
|
||||||
|
|
||||||
if (isEdit && formData.id) {
|
if (isEdit && formData.id) {
|
||||||
await updateBadge(payload)
|
await updateBadge(payload)
|
||||||
|
|||||||
@@ -53,18 +53,12 @@ export default function LevelConfigPage() {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await getLevelConfList()
|
const res = await getLevelConfList()
|
||||||
console.log('等级配置接口返回:', res)
|
|
||||||
|
|
||||||
let list: LevelConf[] = []
|
let list: LevelConf[] = []
|
||||||
|
|
||||||
if (res && Array.isArray(res.data)) {
|
if (res && res.data && Array.isArray(res.data.list)) {
|
||||||
list = res.data
|
list = res.data.list
|
||||||
} else if (res && res.data && Array.isArray((res.data as any).list)) {
|
|
||||||
list = (res.data as any).list
|
|
||||||
} else if (Array.isArray(res)) {
|
|
||||||
list = res as unknown as LevelConf[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by level
|
// Sort by level
|
||||||
list.sort((a, b) => a.level - b.level)
|
list.sort((a, b) => a.level - b.level)
|
||||||
|
|
||||||
|
|||||||
+162
-19
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { Plus, Search, Pencil, Trash2, Leaf, Sun, Droplets, Star, Eye, X } from 'lucide-react'
|
import { Plus, Search, Pencil, Trash2, Leaf, Sun, Droplets, Star, Eye, X } from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -40,6 +40,8 @@ import {
|
|||||||
type Wiki,
|
type Wiki,
|
||||||
type WikiClass,
|
type WikiClass,
|
||||||
type WikiPageParams,
|
type WikiPageParams,
|
||||||
|
uploadWikiImg,
|
||||||
|
deleteWiki,
|
||||||
} from '@/api/wiki'
|
} from '@/api/wiki'
|
||||||
import { uploadFile, type SystemOss } from '@/api/system'
|
import { uploadFile, type SystemOss } from '@/api/system'
|
||||||
|
|
||||||
@@ -74,6 +76,14 @@ interface WikiFormData {
|
|||||||
isHot: number
|
isHot: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LIGHT_TYPES: Record<string, string> = {
|
||||||
|
full_sun: '全日照',
|
||||||
|
partial_sun: '半日照',
|
||||||
|
scattered_light: '明亮散射光',
|
||||||
|
semi_shade: '半阴',
|
||||||
|
shade: '耐阴',
|
||||||
|
}
|
||||||
|
|
||||||
const defaultFormData: WikiFormData = {
|
const defaultFormData: WikiFormData = {
|
||||||
name: '',
|
name: '',
|
||||||
latinName: '',
|
latinName: '',
|
||||||
@@ -124,6 +134,11 @@ export default function PlantsPage() {
|
|||||||
const [isEdit, setIsEdit] = useState(false)
|
const [isEdit, setIsEdit] = useState(false)
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
|
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||||
|
|
||||||
|
// List Upload State
|
||||||
|
const listUploadInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [uploadingWikiId, setUploadingWikiId] = useState<string | null>(null)
|
||||||
|
|
||||||
// 获取植物列表
|
// 获取植物列表
|
||||||
const fetchPlants = async () => {
|
const fetchPlants = async () => {
|
||||||
@@ -133,6 +148,7 @@ export default function PlantsPage() {
|
|||||||
const res = await getWikiPage(searchParams)
|
const res = await getWikiPage(searchParams)
|
||||||
setPlants(res.data?.list || [])
|
setPlants(res.data?.list || [])
|
||||||
setTotal(res.data?.total || 0)
|
setTotal(res.data?.total || 0)
|
||||||
|
setSelectedIds([])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取植物列表失败:', err)
|
console.error('获取植物列表失败:', err)
|
||||||
setError('获取植物列表失败')
|
setError('获取植物列表失败')
|
||||||
@@ -241,6 +257,45 @@ export default function PlantsPage() {
|
|||||||
setDeleteDialogOpen(true)
|
setDeleteDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = () => {
|
||||||
|
if (selectedIds.length === 0) return
|
||||||
|
setSelectedPlant(null)
|
||||||
|
setDeleteDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行删除
|
||||||
|
const handleDeleteExecute = async () => {
|
||||||
|
try {
|
||||||
|
const ids = selectedPlant ? [selectedPlant.id] : selectedIds
|
||||||
|
await deleteWiki(ids)
|
||||||
|
setDeleteDialogOpen(false)
|
||||||
|
fetchPlants()
|
||||||
|
setSelectedPlant(null)
|
||||||
|
// fetchPlants already clears selectedIds
|
||||||
|
} catch (e) {
|
||||||
|
console.error('删除失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择全部
|
||||||
|
const handleSelectAll = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setSelectedIds(plants.map(p => p.id))
|
||||||
|
} else {
|
||||||
|
setSelectedIds([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择单行
|
||||||
|
const handleSelectRow = (id: string, checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
setSelectedIds(prev => [...prev, id])
|
||||||
|
} else {
|
||||||
|
setSelectedIds(prev => prev.filter(i => i !== id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 上传图片
|
// 上传图片
|
||||||
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = e.target.files
|
const files = e.target.files
|
||||||
@@ -272,6 +327,38 @@ export default function PlantsPage() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 列表页点击上传
|
||||||
|
const handleListImageClick = (plant: Wiki) => {
|
||||||
|
if (!plant.imgList?.[0]?.url) {
|
||||||
|
setUploadingWikiId(plant.id)
|
||||||
|
listUploadInputRef.current?.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleListFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
if (!file || !uploadingWikiId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await uploadFile(file)
|
||||||
|
const ossId = res.data.file.id
|
||||||
|
|
||||||
|
await uploadWikiImg({
|
||||||
|
id: uploadingWikiId,
|
||||||
|
ossIds: [ossId]
|
||||||
|
})
|
||||||
|
|
||||||
|
fetchPlants()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('上传图片失败:', err)
|
||||||
|
} finally {
|
||||||
|
setUploadingWikiId(null)
|
||||||
|
if (listUploadInputRef.current) {
|
||||||
|
listUploadInputRef.current.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formData.name) {
|
if (!formData.name) {
|
||||||
@@ -292,6 +379,8 @@ export default function PlantsPage() {
|
|||||||
}
|
}
|
||||||
if (isEdit && formData.id) {
|
if (isEdit && formData.id) {
|
||||||
await updateWiki({ ...data, id: formData.id })
|
await updateWiki({ ...data, id: formData.id })
|
||||||
|
// Sync images using the dedicated API
|
||||||
|
await uploadWikiImg({ id: formData.id, ossIds: formData.ossIds })
|
||||||
} else {
|
} else {
|
||||||
await addWiki(data)
|
await addWiki(data)
|
||||||
}
|
}
|
||||||
@@ -328,11 +417,19 @@ export default function PlantsPage() {
|
|||||||
<h1 className="text-3xl font-bold tracking-tight">植物百科</h1>
|
<h1 className="text-3xl font-bold tracking-tight">植物百科</h1>
|
||||||
<p className="text-muted-foreground">管理植物百科信息</p>
|
<p className="text-muted-foreground">管理植物百科信息</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{selectedIds.length > 0 && (
|
||||||
|
<Button variant="destructive" onClick={handleBatchDelete}>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
批量删除 ({selectedIds.length})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button onClick={handleAdd}>
|
<Button onClick={handleAdd}>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
新增植物
|
新增植物
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 错误提示 */}
|
{/* 错误提示 */}
|
||||||
{error && (
|
{error && (
|
||||||
@@ -393,6 +490,12 @@ export default function PlantsPage() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHead className="w-[50px]">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedIds.length === plants.length && plants.length > 0}
|
||||||
|
onCheckedChange={(checked) => handleSelectAll(checked as boolean)}
|
||||||
|
/>
|
||||||
|
</TableHead>
|
||||||
<TableHead>植物信息</TableHead>
|
<TableHead>植物信息</TableHead>
|
||||||
<TableHead>分类</TableHead>
|
<TableHead>分类</TableHead>
|
||||||
<TableHead>特性</TableHead>
|
<TableHead>特性</TableHead>
|
||||||
@@ -411,6 +514,12 @@ export default function PlantsPage() {
|
|||||||
) : (
|
) : (
|
||||||
plants.map(plant => (
|
plants.map(plant => (
|
||||||
<TableRow key={plant.id}>
|
<TableRow key={plant.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedIds.includes(plant.id)}
|
||||||
|
onCheckedChange={(checked) => handleSelectRow(plant.id, checked as boolean)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{plant.imgList?.[0]?.url ? (
|
{plant.imgList?.[0]?.url ? (
|
||||||
@@ -420,8 +529,21 @@ export default function PlantsPage() {
|
|||||||
className="h-12 w-12 rounded-lg object-cover"
|
className="h-12 w-12 rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-12 w-12 rounded-lg bg-green-100 dark:bg-green-900/20 flex items-center justify-center">
|
<div
|
||||||
<Leaf className="h-6 w-6 text-green-600" />
|
className="h-12 w-12 rounded-lg bg-green-100 dark:bg-green-900/20 flex items-center justify-center cursor-pointer hover:bg-green-200 transition-colors relative group"
|
||||||
|
onClick={() => handleListImageClick(plant)}
|
||||||
|
title="点击上传图片"
|
||||||
|
>
|
||||||
|
{uploadingWikiId === plant.id ? (
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-600"></div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Leaf className="h-6 w-6 text-green-600 group-hover:opacity-50 transition-opacity" />
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<Plus className="h-5 w-5 text-green-700 font-bold" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
@@ -446,7 +568,7 @@ export default function PlantsPage() {
|
|||||||
{plant.lightType && (
|
{plant.lightType && (
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
<Sun className="h-3 w-3" />
|
<Sun className="h-3 w-3" />
|
||||||
{plant.lightType}
|
{LIGHT_TYPES[plant.lightType] || plant.lightType}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{plant.lifeCycle && (
|
{plant.lifeCycle && (
|
||||||
@@ -618,10 +740,11 @@ export default function PlantsPage() {
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>分布区域</Label>
|
<Label>分布区域</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.distributionArea}
|
value={formData.distributionArea}
|
||||||
onChange={e => setFormData({ ...formData, distributionArea: e.target.value })}
|
onChange={e => setFormData({ ...formData, distributionArea: e.target.value })}
|
||||||
placeholder="如:热带亚洲"
|
placeholder="如:热带亚洲"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -714,52 +837,58 @@ export default function PlantsPage() {
|
|||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>叶片类型</Label>
|
<Label>叶片类型</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.foliageType}
|
value={formData.foliageType}
|
||||||
onChange={e => setFormData({ ...formData, foliageType: e.target.value })}
|
onChange={e => setFormData({ ...formData, foliageType: e.target.value })}
|
||||||
placeholder="如:常绿"
|
placeholder="如:常绿"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>叶片颜色</Label>
|
<Label>叶片颜色</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.foliageColor}
|
value={formData.foliageColor}
|
||||||
onChange={e => setFormData({ ...formData, foliageColor: e.target.value })}
|
onChange={e => setFormData({ ...formData, foliageColor: e.target.value })}
|
||||||
placeholder="如:绿色"
|
placeholder="如:绿色"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>叶片形状</Label>
|
<Label>叶片形状</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.foliageShape}
|
value={formData.foliageShape}
|
||||||
onChange={e => setFormData({ ...formData, foliageShape: e.target.value })}
|
onChange={e => setFormData({ ...formData, foliageShape: e.target.value })}
|
||||||
placeholder="如:心形"
|
placeholder="如:心形"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>花期</Label>
|
<Label>花期</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.floweringPeriod}
|
value={formData.floweringPeriod}
|
||||||
onChange={e => setFormData({ ...formData, floweringPeriod: e.target.value })}
|
onChange={e => setFormData({ ...formData, floweringPeriod: e.target.value })}
|
||||||
placeholder="如:春季"
|
placeholder="如:春季"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>花色</Label>
|
<Label>花色</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.floweringColor}
|
value={formData.floweringColor}
|
||||||
onChange={e => setFormData({ ...formData, floweringColor: e.target.value })}
|
onChange={e => setFormData({ ...formData, floweringColor: e.target.value })}
|
||||||
placeholder="如:白色"
|
placeholder="如:白色"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>花形</Label>
|
<Label>花形</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.floweringShape}
|
value={formData.floweringShape}
|
||||||
onChange={e => setFormData({ ...formData, floweringShape: e.target.value })}
|
onChange={e => setFormData({ ...formData, floweringShape: e.target.value })}
|
||||||
placeholder="如:佛焰苞"
|
placeholder="如:佛焰苞"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -774,18 +903,20 @@ export default function PlantsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>果实</Label>
|
<Label>果实</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.fruit}
|
value={formData.fruit}
|
||||||
onChange={e => setFormData({ ...formData, fruit: e.target.value })}
|
onChange={e => setFormData({ ...formData, fruit: e.target.value })}
|
||||||
placeholder="如:浆果"
|
placeholder="如:浆果"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>茎</Label>
|
<Label>茎</Label>
|
||||||
<Input
|
<Textarea
|
||||||
value={formData.stem}
|
value={formData.stem}
|
||||||
onChange={e => setFormData({ ...formData, stem: e.target.value })}
|
onChange={e => setFormData({ ...formData, stem: e.target.value })}
|
||||||
placeholder="如:藤本"
|
placeholder="如:藤本"
|
||||||
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -823,9 +954,9 @@ export default function PlantsPage() {
|
|||||||
<SelectValue placeholder="选择光照类型" />
|
<SelectValue placeholder="选择光照类型" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="full_sun">全日照</SelectItem>
|
{Object.entries(LIGHT_TYPES).map(([value, label]) => (
|
||||||
<SelectItem value="partial_sun">半日照</SelectItem>
|
<SelectItem key={value} value={value}>{label}</SelectItem>
|
||||||
<SelectItem value="shade">耐阴</SelectItem>
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@@ -963,7 +1094,7 @@ export default function PlantsPage() {
|
|||||||
<div><span className="text-muted-foreground">生命周期:</span>{selectedPlant.lifeCycle}</div>
|
<div><span className="text-muted-foreground">生命周期:</span>{selectedPlant.lifeCycle}</div>
|
||||||
)}
|
)}
|
||||||
{selectedPlant.lightType && (
|
{selectedPlant.lightType && (
|
||||||
<div><span className="text-muted-foreground">光照:</span>{selectedPlant.lightType}</div>
|
<div><span className="text-muted-foreground">光照:</span>{LIGHT_TYPES[selectedPlant.lightType] || selectedPlant.lightType}</div>
|
||||||
)}
|
)}
|
||||||
{selectedPlant.optimalTempPeriod && (
|
{selectedPlant.optimalTempPeriod && (
|
||||||
<div><span className="text-muted-foreground">适温:</span>{selectedPlant.optimalTempPeriod}</div>
|
<div><span className="text-muted-foreground">适温:</span>{selectedPlant.optimalTempPeriod}</div>
|
||||||
@@ -993,19 +1124,31 @@ export default function PlantsPage() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>确认删除</DialogTitle>
|
<DialogTitle>确认删除</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
确定要删除植物 "{selectedPlant?.name}" 吗?此操作不可撤销。
|
{selectedPlant
|
||||||
|
? `确定要删除植物 "${selectedPlant.name}" 吗?此操作不可撤销。`
|
||||||
|
: `确定要删除选中的 ${selectedIds.length} 个植物吗?此操作不可撤销。`
|
||||||
|
}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
|
<Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="destructive" onClick={() => setDeleteDialogOpen(false)}>
|
<Button variant="destructive" onClick={handleDeleteExecute}>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Hidden Input for List Upload */}
|
||||||
|
<Input
|
||||||
|
ref={listUploadInputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className="hidden"
|
||||||
|
onChange={handleListFileUpload}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user