385 lines
10 KiB
TypeScript
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>
|
|
);
|
|
} |