子仓库提交
This commit is contained in:
385
src/app/(app)/central-config/system/dictionary/page.tsx
Normal file
385
src/app/(app)/central-config/system/dictionary/page.tsx
Normal file
@@ -0,0 +1,385 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user