diff --git a/src/pages/admin/DictManagement.css b/src/pages/admin/DictManagement.css
new file mode 100644
index 0000000..6906a4c
--- /dev/null
+++ b/src/pages/admin/DictManagement.css
@@ -0,0 +1,331 @@
+/* 字典管理页面样式 - 左右布局 */
+.dict-management {
+ padding: 1.5rem;
+ background: #f8fafc;
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+/* 头部 */
+.dict-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding: 1.5rem;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.dict-header-left {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.dict-header-left svg {
+ color: #667eea;
+}
+
+.dict-header-left h2 {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.dict-header-left p {
+ margin: 0.25rem 0 0 0;
+ font-size: 0.875rem;
+ color: #64748b;
+}
+
+/* 主布局 - 左右分栏 */
+.dict-main-layout {
+ display: grid;
+ grid-template-columns: 380px 1fr;
+ gap: 1.5rem;
+ flex: 1;
+ min-height: 0;
+}
+
+/* 左侧面板 */
+.dict-left-panel {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.dict-tree-card {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.dict-tree-card .ant-card-body {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ padding: 1rem;
+}
+
+/* 字典类型选择器 */
+.dict-type-selector {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 1rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+.dict-type-selector label {
+ font-weight: 500;
+ color: #475569;
+ white-space: nowrap;
+}
+
+/* 树形容器 */
+.dict-tree-container {
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
+}
+
+.dict-tree-container .ant-tree {
+ background: transparent;
+}
+
+.dict-tree-container .ant-tree-node-content-wrapper {
+ padding: 4px 8px;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+}
+
+.dict-tree-container .ant-tree-node-content-wrapper:hover {
+ background: #f1f5f9;
+}
+
+.dict-tree-container .ant-tree-node-selected .ant-tree-node-content-wrapper {
+ background: #e0e7ff !important;
+ color: #667eea;
+}
+
+/* 树节点标题样式 */
+.tree-node-title {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.9rem;
+}
+
+.tree-node-title svg {
+ color: #64748b;
+ flex-shrink: 0;
+}
+
+.tree-node-title span {
+ flex-shrink: 0;
+}
+
+.tree-node-code {
+ color: #94a3b8;
+ font-size: 0.8rem;
+ margin-left: 0.25rem;
+}
+
+/* 右侧面板 */
+.dict-right-panel {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.dict-form-card {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+.dict-form-card .ant-card-body {
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
+ padding: 1.5rem;
+}
+
+/* Card 头部样式 */
+.panel-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.panel-header svg {
+ color: #667eea;
+}
+
+/* Card 样式 */
+.dict-management .ant-card {
+ border-radius: 12px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.dict-management .ant-card-head {
+ border-bottom: 1px solid #e2e8f0;
+ padding: 1rem 1.5rem;
+ min-height: auto;
+}
+
+.dict-management .ant-card-head-title {
+ padding: 0;
+}
+
+.dict-management .ant-card-extra {
+ padding: 0;
+}
+
+/* 按钮样式 */
+.dict-management .ant-btn-primary {
+ background: #667eea;
+ border-color: #667eea;
+}
+
+.dict-management .ant-btn-primary:hover {
+ background: #5568d3;
+ border-color: #5568d3;
+}
+
+/* Form 样式 */
+.dict-management .ant-form-item-label > label {
+ font-weight: 500;
+ color: #475569;
+}
+
+.dict-management .ant-input,
+.dict-management .ant-input-number,
+.dict-management .ant-select-selector {
+ border-radius: 6px;
+}
+
+.dict-management .ant-input:focus,
+.dict-management .ant-input-number:focus,
+.dict-management .ant-select-focused .ant-select-selector {
+ border-color: #667eea;
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
+}
+
+.dict-management .ant-input:disabled,
+.dict-management .ant-select-disabled .ant-select-selector {
+ background: #f8fafc;
+ color: #94a3b8;
+}
+
+/* 行内表单组 */
+.form-inline-group {
+ display: flex;
+ gap: 2rem;
+ align-items: center;
+ padding: 1rem;
+ background: #f8fafc;
+ border-radius: 8px;
+ margin-top: 0.5rem;
+}
+
+.form-inline-item {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.form-inline-item label {
+ font-weight: 500;
+ color: #475569;
+ white-space: nowrap;
+ margin: 0;
+}
+
+/* Select 样式 */
+.dict-management .ant-select-dropdown {
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+/* Switch 样式 */
+.dict-management .ant-switch-checked {
+ background-color: #10b981;
+}
+
+/* Empty 样式 */
+.dict-management .ant-empty {
+ margin: 2rem 0;
+}
+
+/* Popconfirm 样式 */
+.dict-management .ant-popover-inner {
+ border-radius: 8px;
+}
+
+/* 滚动条样式 */
+.dict-tree-container::-webkit-scrollbar,
+.dict-form-card .ant-card-body::-webkit-scrollbar {
+ width: 6px;
+}
+
+.dict-tree-container::-webkit-scrollbar-track,
+.dict-form-card .ant-card-body::-webkit-scrollbar-track {
+ background: #f1f5f9;
+ border-radius: 3px;
+}
+
+.dict-tree-container::-webkit-scrollbar-thumb,
+.dict-form-card .ant-card-body::-webkit-scrollbar-thumb {
+ background: #cbd5e1;
+ border-radius: 3px;
+}
+
+.dict-tree-container::-webkit-scrollbar-thumb:hover,
+.dict-form-card .ant-card-body::-webkit-scrollbar-thumb:hover {
+ background: #94a3b8;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+ .dict-main-layout {
+ grid-template-columns: 320px 1fr;
+ }
+}
+
+@media (max-width: 968px) {
+ .dict-main-layout {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto 1fr;
+ }
+
+ .dict-left-panel {
+ max-height: 400px;
+ }
+}
+
+@media (max-width: 768px) {
+ .dict-management {
+ padding: 1rem;
+ }
+
+ .dict-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+
+ .dict-main-layout {
+ gap: 1rem;
+ }
+}
diff --git a/src/pages/admin/DictManagement.jsx b/src/pages/admin/DictManagement.jsx
new file mode 100644
index 0000000..66f11e2
--- /dev/null
+++ b/src/pages/admin/DictManagement.jsx
@@ -0,0 +1,407 @@
+import React, { useState, useEffect } from 'react';
+import { Tree, Button, Form, Input, InputNumber, Select, Switch, Space, message, Card, Empty, Popconfirm } from 'antd';
+import { BookText, Plus, Save, Trash2, FolderTree, FileText, X } from 'lucide-react';
+import apiClient from '../../utils/apiClient';
+import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
+import './DictManagement.css';
+
+const { Option } = Select;
+const { TextArea } = Input;
+
+const DictManagement = () => {
+ const [loading, setLoading] = useState(false);
+ const [dictTypes, setDictTypes] = useState([]); // 字典类型列表
+ const [selectedDictType, setSelectedDictType] = useState('client_platform'); // 当前选中的字典类型
+ const [dictData, setDictData] = useState([]); // 当前字典类型的数据
+ const [treeData, setTreeData] = useState([]); // 树形结构数据
+ const [selectedNode, setSelectedNode] = useState(null); // 当前选中的节点
+ const [isEditing, setIsEditing] = useState(false); // 是否处于编辑状态
+ const [form] = Form.useForm();
+
+ // 获取所有字典类型
+ const fetchDictTypes = async () => {
+ try {
+ const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.TYPES));
+ if (response.code === '200') {
+ setDictTypes(response.data.types);
+ }
+ } catch (error) {
+ message.error('获取字典类型失败');
+ }
+ };
+
+ // 获取指定类型的字典数据
+ const fetchDictData = async (dictType) => {
+ setLoading(true);
+ try {
+ const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_TYPE(dictType)));
+ if (response.code === '200') {
+ setDictData(response.data.items);
+
+ // 转换为 antd Tree 需要的格式
+ const antdTreeData = buildAntdTreeData(response.data.tree);
+ setTreeData(antdTreeData);
+
+ // 清空选中节点
+ setSelectedNode(null);
+ setIsEditing(false);
+ form.resetFields();
+ }
+ } catch (error) {
+ message.error('获取字典数据失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 将树形数据转换为 antd Tree 组件格式
+ const buildAntdTreeData = (tree) => {
+ return tree.map(node => ({
+ title: (
+
+ {node.parent_code === 'ROOT' ? : }
+ {node.label_cn}
+ ({node.dict_code})
+
+ ),
+ key: node.dict_code,
+ data: node,
+ children: node.children && node.children.length > 0 ? buildAntdTreeData(node.children) : []
+ }));
+ };
+
+ useEffect(() => {
+ fetchDictTypes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedDictType) {
+ fetchDictData(selectedDictType);
+ }
+ }, [selectedDictType]);
+
+ // 选中树节点
+ const handleSelectNode = (selectedKeys, info) => {
+ if (selectedKeys.length > 0) {
+ const nodeData = info.node.data;
+ setSelectedNode(nodeData);
+ setIsEditing(true);
+
+ // 填充表单
+ form.setFieldsValue({
+ dict_type: nodeData.dict_type,
+ dict_code: nodeData.dict_code,
+ parent_code: nodeData.parent_code,
+ label_cn: nodeData.label_cn,
+ label_en: nodeData.label_en,
+ sort_order: nodeData.sort_order,
+ extension_attr: nodeData.extension_attr ? JSON.stringify(nodeData.extension_attr, null, 2) : '',
+ is_default: nodeData.is_default === 1,
+ status: nodeData.status
+ });
+ }
+ };
+
+ // 新增节点
+ const handleAddNode = () => {
+ setSelectedNode(null);
+ setIsEditing(true);
+ form.resetFields();
+ form.setFieldsValue({
+ dict_type: selectedDictType,
+ parent_code: 'ROOT',
+ sort_order: 0,
+ status: 1,
+ is_default: false
+ });
+ };
+
+ // 保存
+ const handleSave = async () => {
+ try {
+ const values = await form.validateFields();
+
+ // 解析 extension_attr JSON
+ if (values.extension_attr) {
+ try {
+ values.extension_attr = JSON.parse(values.extension_attr);
+ } catch (e) {
+ message.error('扩展属性 JSON 格式错误');
+ return;
+ }
+ }
+
+ // 转换 is_default 为数字
+ values.is_default = values.is_default ? 1 : 0;
+
+ if (selectedNode) {
+ // 更新
+ await apiClient.put(
+ buildApiUrl(API_ENDPOINTS.DICT_DATA.UPDATE(selectedNode.id)),
+ values
+ );
+ message.success('更新成功');
+ } else {
+ // 新增
+ await apiClient.post(buildApiUrl(API_ENDPOINTS.DICT_DATA.CREATE), values);
+ message.success('创建成功');
+ }
+
+ // 重新加载数据
+ fetchDictData(selectedDictType);
+ } catch (error) {
+ if (error.errorFields) {
+ // 表单验证错误
+ return;
+ }
+ message.error(selectedNode ? '更新失败' : '创建失败');
+ }
+ };
+
+ // 删除
+ const handleDelete = async () => {
+ if (!selectedNode) return;
+
+ try {
+ await apiClient.delete(buildApiUrl(API_ENDPOINTS.DICT_DATA.DELETE(selectedNode.id)));
+ message.success('删除成功');
+
+ // 重新加载数据
+ setSelectedNode(null);
+ setIsEditing(false);
+ form.resetFields();
+ fetchDictData(selectedDictType);
+ } catch (error) {
+ message.error('删除失败:' + (error.message || '未知错误'));
+ }
+ };
+
+ // 取消编辑
+ const handleCancel = () => {
+ setIsEditing(false);
+ setSelectedNode(null);
+ form.resetFields();
+ };
+
+ // 获取父级选项(用于新增/编辑时选择父级)
+ const getParentOptions = () => {
+ const options = [{ label: 'ROOT(顶级)', value: 'ROOT' }];
+ dictData.forEach(item => {
+ if (item.parent_code === 'ROOT') {
+ options.push({ label: `${item.label_cn} (${item.dict_code})`, value: item.dict_code });
+ }
+ });
+ return options;
+ };
+
+ return (
+
+
+
+
+
+
字典管理
+
管理系统中的码表数据(树形结构)
+
+
+
+
+
+ {/* 左侧面板 */}
+
+
+
+ 字典树
+
+ }
+ extra={
+
}
+ onClick={handleAddNode}
+ >
+ 新增
+
+ }
+ bordered={false}
+ className="dict-tree-card"
+ >
+
+
+
+
+
+
+ {treeData.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* 右侧面板 */}
+
+
+
+ {selectedNode ? '编辑字典项' : isEditing ? '新增字典项' : '字典详情'}
+
+ }
+ extra={
+ isEditing && (
+
+ {selectedNode && (
+
+ }
+ >
+ 删除
+
+
+ )}
+ } onClick={handleCancel}>
+ 取消
+
+ }
+ onClick={handleSave}
+ >
+ 保存
+
+
+ )
+ }
+ bordered={false}
+ className="dict-form-card"
+ >
+ {isEditing ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+