Files
smart-cropx-ui/src/app/(app)/central-config/system/dictionary/page.tsx
2025-11-10 09:19:56 +08:00

385 lines
10 KiB
TypeScript

'use client';
import React, { useReducer, useLayoutEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Plus, Download } from 'lucide-react';
import { toast } from 'sonner';
import { CategoryList } from './components/CategoryList';
import { CategoryForm } from './components/CategoryForm';
import { DeleteConfirmDialog } from './components/DeleteConfirmDialog';
import { CategoryDictionary } from './types';
import { categoryReducer, initialCategoryState } from './reducer';
// 模拟数据
const mockData: CategoryDictionary[] = [
// 性别
{
id: 'dict-1',
code: 'GENDER_MALE',
name: '性别-男',
category: 'gender',
value: 'male',
label: '男',
sortOrder: 1,
isSystem: true,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-2',
code: 'GENDER_FEMALE',
name: '性别-女',
category: 'gender',
value: 'female',
label: '女',
sortOrder: 2,
isSystem: true,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
// 状态
{
id: 'dict-3',
code: 'STATUS_ACTIVE',
name: '状态-激活',
category: 'status',
value: 'active',
label: '激活',
sortOrder: 1,
isSystem: true,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-4',
code: 'STATUS_INACTIVE',
name: '状态-停用',
category: 'status',
value: 'inactive',
label: '停用',
sortOrder: 2,
isSystem: true,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
// 单位类型
{
id: 'dict-5',
code: 'UNIT_AREA_MU',
name: '面积单位-亩',
category: 'unit',
value: 'mu',
label: '亩',
sortOrder: 1,
description: '中国传统面积单位',
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-6',
code: 'UNIT_AREA_HECTARE',
name: '面积单位-公顷',
category: 'unit',
value: 'hectare',
label: '公顷',
sortOrder: 2,
description: '国际通用面积单位',
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-7',
code: 'UNIT_WEIGHT_KG',
name: '重量单位-千克',
category: 'unit',
value: 'kg',
label: '千克',
sortOrder: 3,
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-8',
code: 'UNIT_WEIGHT_TON',
name: '重量单位-吨',
category: 'unit',
value: 'ton',
label: '吨',
sortOrder: 4,
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
// 天气
{
id: 'dict-9',
code: 'WEATHER_SUNNY',
name: '天气-晴',
category: 'weather',
value: 'sunny',
label: '晴',
sortOrder: 1,
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-10',
code: 'WEATHER_CLOUDY',
name: '天气-多云',
category: 'weather',
value: 'cloudy',
label: '多云',
sortOrder: 2,
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-11',
code: 'WEATHER_RAINY',
name: '天气-雨',
category: 'weather',
value: 'rainy',
label: '雨',
sortOrder: 3,
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
// 土壤类型
{
id: 'dict-12',
code: 'SOIL_SANDY',
name: '土壤-砂土',
category: 'soil_type',
value: 'sandy',
label: '砂土',
sortOrder: 1,
description: '含砂粒较多的土壤',
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-13',
code: 'SOIL_LOAMY',
name: '土壤-壤土',
category: 'soil_type',
value: 'loamy',
label: '壤土',
sortOrder: 2,
description: '砂粘适中的土壤',
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
{
id: 'dict-14',
code: 'SOIL_CLAY',
name: '土壤-黏土',
category: 'soil_type',
value: 'clay',
label: '黏土',
sortOrder: 3,
description: '含黏粒较多的土壤',
isSystem: false,
isActive: true,
createdAt: '2024-01-01T00:00:00',
updatedAt: '2024-01-01T00:00:00',
},
];
export default function DataDictionaryPage() {
const [state, dispatch] = useReducer(categoryReducer, initialCategoryState);
const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
const [categoryToDelete, setCategoryToDelete] = React.useState<CategoryDictionary | null>(null);
// 加载数据
useLayoutEffect(() => {
const data = localStorage.getItem('smart_agriculture_category_dictionary');
if (data) {
try {
const categories = JSON.parse(data);
dispatch({ type: 'SET_CATEGORIES', payload: categories });
} catch (error) {
console.error('Failed to parse category dictionary data:', error);
loadMockData();
}
} else {
loadMockData();
}
}, []);
const loadMockData = () => {
localStorage.setItem('smart_agriculture_category_dictionary', JSON.stringify(mockData));
dispatch({ type: 'SET_CATEGORIES', payload: mockData });
};
const saveCategories = (categories: CategoryDictionary[]) => {
localStorage.setItem('smart_agriculture_category_dictionary', JSON.stringify(categories));
dispatch({ type: 'SET_CATEGORIES', payload: categories });
};
// 处理新增
const handleAdd = () => {
dispatch({ type: 'SET_DIALOG_STATE', payload: { open: true, editing: null } });
};
// 处理编辑
const handleEdit = (category: CategoryDictionary) => {
dispatch({ type: 'SET_DIALOG_STATE', payload: { open: true, editing: category } });
};
// 处理删除
const handleDelete = (id: string) => {
const category = state.categories.find(c => c.id === id);
if (!category) return;
if (category.isSystem) {
toast.error('系统内置字典不能删除');
return;
}
setCategoryToDelete(category);
setDeleteConfirmOpen(true);
};
// 确认删除
const confirmDelete = () => {
if (!categoryToDelete) return;
const updated = state.categories.filter(c => c.id !== categoryToDelete.id);
saveCategories(updated);
toast.success('删除成功');
setCategoryToDelete(null);
};
// 处理保存
const handleSave = () => {
const { formData, dialogState } = state;
if (!formData.code.trim() || !formData.name.trim() || !formData.value.trim() || !formData.label.trim()) {
toast.error('请填写必填项');
return;
}
const now = new Date().toISOString();
if (dialogState.editing) {
// 编辑
const updated = state.categories.map(category =>
category.id === dialogState.editing!.id
? {
...category,
...formData,
updatedAt: now,
}
: category
);
saveCategories(updated);
toast.success('更新成功');
} else {
// 新增
const newCategory: CategoryDictionary = {
id: `dict-${Date.now()}`,
...formData,
isSystem: false,
createdAt: now,
updatedAt: now,
};
saveCategories([...state.categories, newCategory]);
toast.success('添加成功');
}
dispatch({ type: 'SET_DIALOG_STATE', payload: { open: false, editing: null } });
};
// 处理导出
const handleExport = () => {
const filteredCategories = state.categories.filter(category => {
const matchKeyword = !state.searchKeyword ||
category.name.includes(state.searchKeyword) ||
category.code.includes(state.searchKeyword) ||
category.label.includes(state.searchKeyword) ||
category.value.includes(state.searchKeyword);
const matchCategory = state.categoryFilter === 'all' || category.category === state.categoryFilter;
return matchKeyword && matchCategory;
});
const dataStr = JSON.stringify(filteredCategories, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `category_dictionary_${new Date().getTime()}.json`;
link.click();
toast.success('导出成功');
};
return (
<div className="space-y-6 p-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-green-800 dark:text-green-600"></h2>
<p className="text-muted-foreground"></p>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={handleExport}>
<Download className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleAdd}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* 字典列表 */}
<CategoryList
categories={state.categories}
searchKeyword={state.searchKeyword}
categoryFilter={state.categoryFilter}
onSearchChange={(keyword) => dispatch({ type: 'SET_SEARCH_KEYWORD', payload: keyword })}
onCategoryFilterChange={(category) => dispatch({ type: 'SET_CATEGORY_FILTER', payload: category })}
onEdit={handleEdit}
onDelete={handleDelete}
/>
{/* 编辑表单 */}
<CategoryForm
open={state.dialogState.open}
editing={state.dialogState.editing}
formData={state.formData}
onFormDataChange={(data) => dispatch({ type: 'SET_FORM_DATA', payload: data })}
onOpenChange={(open) => dispatch({ type: 'SET_DIALOG_STATE', payload: { open, editing: null } })}
onSave={handleSave}
/>
{/* 删除确认对话框 */}
<DeleteConfirmDialog
open={deleteConfirmOpen}
category={categoryToDelete}
onOpenChange={setDeleteConfirmOpen}
onConfirm={confirmDelete}
/>
</div>
);
}