diff --git a/src/api/config.ts b/src/api/config.ts index 72e2a63..20339e5 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -1,4 +1,4 @@ -import { get, post } from '@/lib/request' +import {get, post} from '@/lib/request' // 等级配置接口 export interface LevelConf { @@ -22,6 +22,10 @@ export interface UpdateLevelParams extends Partial { id: string } +export interface ListResponse { + list: LevelConf[]; +} + // 获取等级配置列表 (虽然是 list 接口,但根据以往经验,可能返回带分页的结构或者直接是数组,先假设是 PageResult 或者是 List 结构,Swagger 上没细说。通常 /list 可能是分页的。如果 swagger 没写分页参数,那可能是全量列表) // 根据 swagger "/config/level/list" 没有 parameters,大概率是全量列表。 // 假设返回结构 { data: LevelConf[], msg: string, code: number } @@ -38,12 +42,12 @@ export function updateLevelConf(data: UpdateLevelParams) { // 获取等级配置详情 export function getLevelConfDetail(id: string) { - return get<{ data: LevelConf }>('/config/level/detail', { id }) + return get<{ data: LevelConf }>('/config/level/detail', {id}) } // 获取等级配置列表 export function getLevelConfList() { - return get<{ data: LevelConf[] }>('/config/level/list') + return get<{ data: ListResponse }>('/config/level/list') } // 删除等级配置 (Swagger 里没看到 delete 接口,先不加) diff --git a/src/api/wiki.ts b/src/api/wiki.ts index f40af72..6b3d9af 100644 --- a/src/api/wiki.ts +++ b/src/api/wiki.ts @@ -159,7 +159,18 @@ export function addWiki(data: CreateWikiParams) { return post<{ msg: string }>('/wiki/add', data) } + // 修改百科 export function updateWiki(data: UpdateWikiParams) { 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 }) +} diff --git a/src/pages/plant/community/Topic.tsx b/src/pages/plant/community/Topic.tsx index 3186bf6..5398dfd 100644 --- a/src/pages/plant/community/Topic.tsx +++ b/src/pages/plant/community/Topic.tsx @@ -71,10 +71,10 @@ export default function TopicsPage() { const list = res.data?.list || [] // Map backend snake_case to camelCase if necessary (for robustness) - const mappedList = list.map((item: any) => ({ + const mappedList = list.map((item: Topic) => ({ ...item, - startTime: (item.startTime || item.start_time) ? String(item.startTime || item.start_time) : undefined, - endTime: (item.endTime || item.end_time) ? String(item.endTime || item.end_time) : undefined + startTime: (item.startTime) ? String(item.startTime) : undefined, + endTime: (item.endTime) ? String(item.endTime) : undefined })) as Topic[] setTopics(Array.isArray(mappedList) ? mappedList : []) @@ -108,15 +108,15 @@ export default function TopicsPage() { const handleEdit = async (topic: Topic) => { try { 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({ id: detail.id, title: detail.title || '', remark: detail.remark || '', // backend might return start_time, map to startTime - startTime: detail.startTime || detail.start_time || '', - endTime: detail.endTime || detail.end_time || '', + startTime: detail.startTime || '', + endTime: detail.endTime || '', }) setIsEdit(true) setDialogOpen(true) diff --git a/src/pages/plant/conf/Badge.tsx b/src/pages/plant/conf/Badge.tsx index 05e6a4d..a08d953 100644 --- a/src/pages/plant/conf/Badge.tsx +++ b/src/pages/plant/conf/Badge.tsx @@ -1,5 +1,5 @@ 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 { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -214,7 +214,7 @@ export default function BadgeConfigPage() { setSubmitting(true) try { - const { icon, ...payload } = formData + const { ...payload } = formData if (isEdit && formData.id) { await updateBadge(payload) diff --git a/src/pages/plant/conf/Level.tsx b/src/pages/plant/conf/Level.tsx index f4ae562..c0f456a 100644 --- a/src/pages/plant/conf/Level.tsx +++ b/src/pages/plant/conf/Level.tsx @@ -53,18 +53,12 @@ export default function LevelConfigPage() { setLoading(true) try { const res = await getLevelConfList() - console.log('等级配置接口返回:', res) let list: LevelConf[] = [] - if (res && Array.isArray(res.data)) { - list = res.data - } 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[] + if (res && res.data && Array.isArray(res.data.list)) { + list = res.data.list } - // Sort by level list.sort((a, b) => a.level - b.level) diff --git a/src/pages/plant/wiki/Wiki.tsx b/src/pages/plant/wiki/Wiki.tsx index 6ad5147..ab60adc 100644 --- a/src/pages/plant/wiki/Wiki.tsx +++ b/src/pages/plant/wiki/Wiki.tsx @@ -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 { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -40,6 +40,8 @@ import { type Wiki, type WikiClass, type WikiPageParams, + uploadWikiImg, + deleteWiki, } from '@/api/wiki' import { uploadFile, type SystemOss } from '@/api/system' @@ -74,6 +76,14 @@ interface WikiFormData { isHot: number } +const LIGHT_TYPES: Record = { + full_sun: '全日照', + partial_sun: '半日照', + scattered_light: '明亮散射光', + semi_shade: '半阴', + shade: '耐阴', +} + const defaultFormData: WikiFormData = { name: '', latinName: '', @@ -124,6 +134,11 @@ export default function PlantsPage() { const [isEdit, setIsEdit] = useState(false) const [submitting, setSubmitting] = useState(false) const [uploading, setUploading] = useState(false) + const [selectedIds, setSelectedIds] = useState([]) + + // List Upload State + const listUploadInputRef = useRef(null) + const [uploadingWikiId, setUploadingWikiId] = useState(null) // 获取植物列表 const fetchPlants = async () => { @@ -133,6 +148,7 @@ export default function PlantsPage() { const res = await getWikiPage(searchParams) setPlants(res.data?.list || []) setTotal(res.data?.total || 0) + setSelectedIds([]) } catch (err) { console.error('获取植物列表失败:', err) setError('获取植物列表失败') @@ -241,6 +257,45 @@ export default function PlantsPage() { 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) => { 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) => { + 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 () => { if (!formData.name) { @@ -292,6 +379,8 @@ export default function PlantsPage() { } if (isEdit && formData.id) { await updateWiki({ ...data, id: formData.id }) + // Sync images using the dedicated API + await uploadWikiImg({ id: formData.id, ossIds: formData.ossIds }) } else { await addWiki(data) } @@ -328,10 +417,18 @@ export default function PlantsPage() {

植物百科

管理植物百科信息

- +
+ {selectedIds.length > 0 && ( + + )} + +
{/* 错误提示 */} @@ -393,6 +490,12 @@ export default function PlantsPage() { + + 0} + onCheckedChange={(checked) => handleSelectAll(checked as boolean)} + /> + 植物信息 分类 特性 @@ -411,6 +514,12 @@ export default function PlantsPage() { ) : ( plants.map(plant => ( + + handleSelectRow(plant.id, checked as boolean)} + /> +
{plant.imgList?.[0]?.url ? ( @@ -420,8 +529,21 @@ export default function PlantsPage() { className="h-12 w-12 rounded-lg object-cover" /> ) : ( -
- +
handleListImageClick(plant)} + title="点击上传图片" + > + {uploadingWikiId === plant.id ? ( +
+ ) : ( + <> + +
+ +
+ + )}
)}
@@ -446,7 +568,7 @@ export default function PlantsPage() { {plant.lightType && ( - {plant.lightType} + {LIGHT_TYPES[plant.lightType] || plant.lightType} )} {plant.lifeCycle && ( @@ -618,10 +740,11 @@ export default function PlantsPage() {
- setFormData({ ...formData, distributionArea: e.target.value })} placeholder="如:热带亚洲" + rows={2} />
@@ -714,52 +837,58 @@ export default function PlantsPage() {
- setFormData({ ...formData, foliageType: e.target.value })} placeholder="如:常绿" + rows={2} />
- setFormData({ ...formData, foliageColor: e.target.value })} placeholder="如:绿色" + rows={2} />
- setFormData({ ...formData, foliageShape: e.target.value })} placeholder="如:心形" + rows={2} />
- setFormData({ ...formData, floweringPeriod: e.target.value })} placeholder="如:春季" + rows={2} />
- setFormData({ ...formData, floweringColor: e.target.value })} placeholder="如:白色" + rows={2} />
- setFormData({ ...formData, floweringShape: e.target.value })} placeholder="如:佛焰苞" + rows={2} />
@@ -774,18 +903,20 @@ export default function PlantsPage() {
- setFormData({ ...formData, fruit: e.target.value })} placeholder="如:浆果" + rows={2} />
- setFormData({ ...formData, stem: e.target.value })} placeholder="如:藤本" + rows={2} />
@@ -823,9 +954,9 @@ export default function PlantsPage() { - 全日照 - 半日照 - 耐阴 + {Object.entries(LIGHT_TYPES).map(([value, label]) => ( + {label} + ))}
@@ -963,7 +1094,7 @@ export default function PlantsPage() {
生命周期:{selectedPlant.lifeCycle}
)} {selectedPlant.lightType && ( -
光照:{selectedPlant.lightType}
+
光照:{LIGHT_TYPES[selectedPlant.lightType] || selectedPlant.lightType}
)} {selectedPlant.optimalTempPeriod && (
适温:{selectedPlant.optimalTempPeriod}
@@ -993,19 +1124,31 @@ export default function PlantsPage() { 确认删除 - 确定要删除植物 "{selectedPlant?.name}" 吗?此操作不可撤销。 + {selectedPlant + ? `确定要删除植物 "${selectedPlant.name}" 吗?此操作不可撤销。` + : `确定要删除选中的 ${selectedIds.length} 个植物吗?此操作不可撤销。` + } - + + {/* Hidden Input for List Upload */} +
) }