增加了字段管理
parent
a708031347
commit
86f58c854f
|
|
@ -29,42 +29,24 @@ const ClientDownloads = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const getPlatformIcon = (platformName) => {
|
||||
switch (platformName) {
|
||||
case 'ios':
|
||||
return <Apple size={32} />;
|
||||
case 'android':
|
||||
return <Smartphone size={32} />;
|
||||
case 'mac_intel':
|
||||
case 'mac_m':
|
||||
return <Apple size={32} />;
|
||||
case 'mcu':
|
||||
return <Cpu size={32} />;
|
||||
default:
|
||||
return <Monitor size={32} />;
|
||||
const getPlatformIcon = (platformCode) => {
|
||||
const code = (platformCode || '').toUpperCase();
|
||||
|
||||
// 根据 platform_code 判断图标
|
||||
if (code.includes('IOS') || code.includes('MAC')) {
|
||||
return <Apple size={32} />;
|
||||
} else if (code.includes('ANDROID')) {
|
||||
return <Smartphone size={32} />;
|
||||
} else if (code.includes('TERM') || code.includes('MCU')) {
|
||||
return <Cpu size={32} />;
|
||||
} else {
|
||||
return <Monitor size={32} />;
|
||||
}
|
||||
};
|
||||
|
||||
const getPlatformLabel = (client) => {
|
||||
const platformName = client.platform_name;
|
||||
const platformType = client.platform_type;
|
||||
|
||||
// 根据平台类型和平台名称组合判断
|
||||
if (platformType === 'terminal') {
|
||||
if (platformName === 'android') return 'Android终端';
|
||||
if (platformName === 'mcu') return '单片机';
|
||||
}
|
||||
|
||||
// 默认标签
|
||||
const labels = {
|
||||
ios: 'iOS',
|
||||
android: 'Android',
|
||||
windows: 'Windows',
|
||||
mac_intel: 'Mac (Intel)',
|
||||
mac_m: 'Mac (M系列)',
|
||||
linux: 'Linux'
|
||||
};
|
||||
return labels[platformName] || platformName;
|
||||
// 优先使用 dict_data 的中文标签
|
||||
return client.label_cn || client.platform_code || '未知平台';
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
|
|
@ -109,7 +91,7 @@ const ClientDownloads = () => {
|
|||
className="client-download-card"
|
||||
>
|
||||
<div className="card-icon">
|
||||
{getPlatformIcon(client.platform_name)}
|
||||
{getPlatformIcon(client.platform_code)}
|
||||
</div>
|
||||
<div className="card-info">
|
||||
<h4>{getPlatformLabel(client)}</h4>
|
||||
|
|
@ -149,7 +131,7 @@ const ClientDownloads = () => {
|
|||
className="client-download-card"
|
||||
>
|
||||
<div className="card-icon">
|
||||
{getPlatformIcon(client.platform_name)}
|
||||
{getPlatformIcon(client.platform_code)}
|
||||
</div>
|
||||
<div className="card-info">
|
||||
<h4>{getPlatformLabel(client)}</h4>
|
||||
|
|
@ -189,7 +171,7 @@ const ClientDownloads = () => {
|
|||
className="client-download-card"
|
||||
>
|
||||
<div className="card-icon">
|
||||
{getPlatformIcon(client.platform_name)}
|
||||
{getPlatformIcon(client.platform_code)}
|
||||
</div>
|
||||
<div className="card-info">
|
||||
<h4>{getPlatformLabel(client)}</h4>
|
||||
|
|
|
|||
|
|
@ -67,11 +67,20 @@ const API_CONFIG = {
|
|||
CLIENT_DOWNLOADS: {
|
||||
LIST: '/api/clients',
|
||||
LATEST: '/api/clients/latest',
|
||||
LATEST_BY_PLATFORM: '/api/clients/latest/by-platform',
|
||||
LATEST_BY_PLATFORM: '/api/clients/latest/by-platform', // 支持旧版(platform_type+platform_name)和新版(platform_code)
|
||||
DETAIL: (id) => `/api/clients/${id}`,
|
||||
CREATE: '/api/clients',
|
||||
UPDATE: (id) => `/api/clients/${id}`,
|
||||
DELETE: (id) => `/api/clients/${id}`
|
||||
DELETE: (id) => `/api/clients/${id}`,
|
||||
UPLOAD: '/api/clients/upload'
|
||||
},
|
||||
DICT_DATA: {
|
||||
TYPES: '/api/dict/types',
|
||||
BY_TYPE: (dictType) => `/api/dict/${dictType}`,
|
||||
BY_CODE: (dictType, dictCode) => `/api/dict/${dictType}/${dictCode}`,
|
||||
CREATE: '/api/dict',
|
||||
UPDATE: (id) => `/api/dict/${id}`,
|
||||
DELETE: (id) => `/api/dict/${id}`
|
||||
},
|
||||
VOICEPRINT: {
|
||||
STATUS: (userId) => `/api/voiceprint/${userId}`,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
/* AdminManagement.css */
|
||||
.admin-management-page {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
background: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
|
|
@ -17,14 +16,15 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.admin-wrapper {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Old Tabs styles - can be removed or kept for reference */
|
||||
|
|
@ -78,10 +78,18 @@
|
|||
}
|
||||
|
||||
/* New AntD Tabs Styles */
|
||||
.admin-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.admin-tabs .ant-tabs-nav {
|
||||
padding: 0 2rem;
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.admin-tabs .ant-tabs-tab {
|
||||
|
|
@ -109,7 +117,11 @@
|
|||
|
||||
.admin-tabs .ant-tabs-content-holder {
|
||||
padding: 2rem;
|
||||
min-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.admin-tabs .ant-tabs-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Settings, Users, Smartphone, Shield } from 'lucide-react';
|
||||
import { Settings, Users, Smartphone, Shield, BookText } from 'lucide-react';
|
||||
import { Tabs } from 'antd';
|
||||
import UserManagement from './admin/UserManagement';
|
||||
import SystemConfiguration from './admin/SystemConfiguration';
|
||||
import ClientManagement from './ClientManagement';
|
||||
import PermissionManagement from './admin/PermissionManagement';
|
||||
import DictManagement from './admin/DictManagement';
|
||||
import Breadcrumb from '../components/Breadcrumb';
|
||||
import './AdminManagement.css';
|
||||
|
||||
|
|
@ -30,6 +31,12 @@ const AdminManagement = () => {
|
|||
>
|
||||
<PermissionManagement />
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={<span><BookText size={16} /> 字典管理</span>}
|
||||
key="dictManagement"
|
||||
>
|
||||
<DictManagement />
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={<span><Settings size={16} /> 系统配置</span>}
|
||||
key="systemConfiguration"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
|
||||
.download-page-header .header-content {
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
.download-page-content {
|
||||
flex: 1;
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
|
|
|
|||
|
|
@ -216,14 +216,26 @@
|
|||
}
|
||||
|
||||
.btn-icon {
|
||||
padding: 0.5rem;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-icon svg {
|
||||
display: block;
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
|
|
@ -455,6 +467,60 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 文件上传区域 */
|
||||
.upload-area {
|
||||
border: 2px dashed #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #667eea;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.upload-label {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem 2rem;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.upload-label:hover {
|
||||
background: #5568d3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.upload-label.disabled {
|
||||
background: #94a3b8;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.upload-label.disabled:hover {
|
||||
background: #94a3b8;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: #64748b;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
padding: 1rem 2rem;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
|
|
@ -515,15 +581,6 @@
|
|||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import {
|
|||
Link,
|
||||
FileText,
|
||||
HardDrive,
|
||||
Cpu
|
||||
Cpu,
|
||||
Upload
|
||||
} from 'lucide-react';
|
||||
import apiClient from '../utils/apiClient';
|
||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
||||
|
|
@ -36,10 +37,14 @@ const ClientManagement = ({ user }) => {
|
|||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [expandedNotes, setExpandedNotes] = useState({});
|
||||
const [toasts, setToasts] = useState([]);
|
||||
const [uploadingFile, setUploadingFile] = useState(false);
|
||||
|
||||
// 码表数据
|
||||
const [platforms, setPlatforms] = useState({ tree: [], items: [] });
|
||||
const [platformsMap, setPlatformsMap] = useState({});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
platform_type: 'mobile',
|
||||
platform_name: 'ios',
|
||||
platform_code: '',
|
||||
version: '',
|
||||
version_code: '',
|
||||
download_url: '',
|
||||
|
|
@ -50,23 +55,6 @@ const ClientManagement = ({ user }) => {
|
|||
min_system_version: ''
|
||||
});
|
||||
|
||||
const platformOptions = {
|
||||
mobile: [
|
||||
{ value: 'ios', label: 'iOS', icon: <Apple size={16} /> },
|
||||
{ value: 'android', label: 'Android', icon: <Smartphone size={16} /> }
|
||||
],
|
||||
desktop: [
|
||||
{ value: 'windows', label: 'Windows', icon: <Monitor size={16} /> },
|
||||
{ value: 'mac_intel', label: 'Mac (Intel)', icon: <Apple size={16} /> },
|
||||
{ value: 'mac_m', label: 'Mac (M系列)', icon: <Apple size={16} /> },
|
||||
{ value: 'linux', label: 'Linux', icon: <Monitor size={16} /> }
|
||||
],
|
||||
terminal: [
|
||||
{ value: 'android', label: 'Android终端', icon: <Smartphone size={16} /> },
|
||||
{ value: 'mcu', label: '单片机', icon: <Cpu size={16} /> }
|
||||
]
|
||||
};
|
||||
|
||||
// Toast helper functions
|
||||
const showToast = (message, type = 'info') => {
|
||||
const id = Date.now();
|
||||
|
|
@ -78,9 +66,28 @@ const ClientManagement = ({ user }) => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlatforms();
|
||||
fetchClients();
|
||||
}, []);
|
||||
|
||||
const fetchPlatforms = async () => {
|
||||
try {
|
||||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_TYPE('client_platform')));
|
||||
const { tree, items } = response.data;
|
||||
setPlatforms({ tree, items });
|
||||
|
||||
// 构建快速查找map
|
||||
const map = {};
|
||||
items.forEach(item => {
|
||||
map[item.dict_code] = item;
|
||||
});
|
||||
setPlatformsMap(map);
|
||||
} catch (error) {
|
||||
console.error('获取平台列表失败:', error);
|
||||
showToast('获取平台列表失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const fetchClients = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
|
|
@ -97,13 +104,32 @@ const ClientManagement = ({ user }) => {
|
|||
const handleCreate = async () => {
|
||||
try {
|
||||
// 验证必填字段
|
||||
if (!formData.version_code || !formData.version || !formData.download_url) {
|
||||
if (!formData.platform_code || !formData.version_code || !formData.version || !formData.download_url) {
|
||||
showToast('请填写所有必填字段', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据platform_code映射到旧字段platform_type和platform_name以保持向后兼容
|
||||
const platformInfo = platformsMap[formData.platform_code];
|
||||
const parentCode = platformInfo?.parent_code;
|
||||
const parentInfo = parentCode && parentCode !== 'ROOT' ? platformsMap[parentCode] : null;
|
||||
|
||||
// 映射platform_type
|
||||
let platform_type = 'desktop'; // 默认
|
||||
if (parentInfo) {
|
||||
const parentCodeUpper = parentCode.toUpperCase();
|
||||
if (parentCodeUpper === 'MOBILE') platform_type = 'mobile';
|
||||
else if (parentCodeUpper === 'DESKTOP') platform_type = 'desktop';
|
||||
else if (parentCodeUpper === 'TERMINAL') platform_type = 'terminal';
|
||||
}
|
||||
|
||||
// 映射platform_name (简化映射,用小写的dict_code)
|
||||
const platform_name = formData.platform_code.toLowerCase();
|
||||
|
||||
const payload = {
|
||||
...formData,
|
||||
platform_type,
|
||||
platform_name,
|
||||
version_code: parseInt(formData.version_code, 10),
|
||||
file_size: formData.file_size ? parseInt(formData.file_size, 10) : null
|
||||
};
|
||||
|
|
@ -132,12 +158,13 @@ const ClientManagement = ({ user }) => {
|
|||
const handleUpdate = async () => {
|
||||
try {
|
||||
// 验证必填字段
|
||||
if (!formData.version_code || !formData.version || !formData.download_url) {
|
||||
if (!formData.platform_code || !formData.version_code || !formData.version || !formData.download_url) {
|
||||
showToast('请填写所有必填字段', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
platform_code: formData.platform_code,
|
||||
version: formData.version,
|
||||
version_code: parseInt(formData.version_code, 10),
|
||||
download_url: formData.download_url,
|
||||
|
|
@ -191,8 +218,7 @@ const ClientManagement = ({ user }) => {
|
|||
setIsEditing(true);
|
||||
setSelectedClient(client);
|
||||
setFormData({
|
||||
platform_type: client.platform_type,
|
||||
platform_name: client.platform_name,
|
||||
platform_code: client.platform_code || '',
|
||||
version: client.version,
|
||||
version_code: String(client.version_code),
|
||||
download_url: client.download_url,
|
||||
|
|
@ -205,9 +231,12 @@ const ClientManagement = ({ user }) => {
|
|||
} else {
|
||||
setIsEditing(false);
|
||||
setSelectedClient(null);
|
||||
// 默认选择第一个可用的平台
|
||||
const defaultPlatformCode = platforms.items.length > 0 && platforms.items[0].parent_code !== 'ROOT'
|
||||
? platforms.items[0].dict_code
|
||||
: '';
|
||||
setFormData({
|
||||
platform_type: 'mobile',
|
||||
platform_name: 'ios',
|
||||
platform_code: defaultPlatformCode,
|
||||
version: '',
|
||||
version_code: '',
|
||||
download_url: '',
|
||||
|
|
@ -240,6 +269,54 @@ const ClientManagement = ({ user }) => {
|
|||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleFileUpload = async (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!formData.platform_code) {
|
||||
showToast('请先选择平台', 'warning');
|
||||
event.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadingFile(true);
|
||||
|
||||
try {
|
||||
const uploadFormData = new FormData();
|
||||
uploadFormData.append('file', file);
|
||||
uploadFormData.append('platform_code', formData.platform_code);
|
||||
|
||||
const response = await apiClient.post(
|
||||
buildApiUrl(API_ENDPOINTS.CLIENT_DOWNLOADS.UPLOAD),
|
||||
uploadFormData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { file_size, download_url, version_code, version_name } = response.data;
|
||||
|
||||
// 自动填充表单
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
file_size: file_size ? String(file_size) : prev.file_size,
|
||||
download_url: download_url || prev.download_url,
|
||||
version_code: version_code ? String(version_code) : prev.version_code,
|
||||
version: version_name || prev.version
|
||||
}));
|
||||
|
||||
showToast('文件上传成功,已自动填充相关字段', 'success');
|
||||
} catch (error) {
|
||||
console.error('文件上传失败:', error);
|
||||
showToast(error.response?.data?.message || '文件上传失败', 'error');
|
||||
} finally {
|
||||
setUploadingFile(false);
|
||||
event.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const openEditModal = (client) => {
|
||||
handleOpenModal(client);
|
||||
};
|
||||
|
|
@ -247,15 +324,17 @@ const ClientManagement = ({ user }) => {
|
|||
const openDeleteModal = (client) => {
|
||||
setDeleteConfirmInfo({
|
||||
id: client.id,
|
||||
platform_name: getPlatformLabel(client.platform_name),
|
||||
platform_name: getPlatformLabel(client.platform_code),
|
||||
version: client.version
|
||||
});
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
const defaultPlatformCode = platforms.items.length > 0 && platforms.items[0].parent_code !== 'ROOT'
|
||||
? platforms.items[0].dict_code
|
||||
: '';
|
||||
setFormData({
|
||||
platform_type: 'mobile',
|
||||
platform_name: 'ios',
|
||||
platform_code: defaultPlatformCode,
|
||||
version: '',
|
||||
version_code: '',
|
||||
download_url: '',
|
||||
|
|
@ -268,10 +347,9 @@ const ClientManagement = ({ user }) => {
|
|||
setSelectedClient(null);
|
||||
};
|
||||
|
||||
const getPlatformLabel = (platformName) => {
|
||||
const allOptions = [...platformOptions.mobile, ...platformOptions.desktop, ...platformOptions.terminal];
|
||||
const option = allOptions.find(opt => opt.value === platformName);
|
||||
return option ? option.label : platformName;
|
||||
const getPlatformLabel = (platformCode) => {
|
||||
const platform = platformsMap[platformCode];
|
||||
return platform ? platform.label_cn : platformCode;
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
|
|
@ -295,7 +373,7 @@ const ClientManagement = ({ user }) => {
|
|||
const query = searchQuery.toLowerCase();
|
||||
return (
|
||||
client.version.toLowerCase().includes(query) ||
|
||||
getPlatformLabel(client.platform_name).toLowerCase().includes(query) ||
|
||||
getPlatformLabel(client.platform_code).toLowerCase().includes(query) ||
|
||||
(client.release_notes && client.release_notes.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
|
@ -398,9 +476,9 @@ const ClientManagement = ({ user }) => {
|
|||
<div key={client.id} className={`client-card ${!client.is_active ? 'inactive' : ''}`}>
|
||||
<div className="card-header">
|
||||
<div className="platform-info">
|
||||
<h3>{getPlatformLabel(client.platform_name)}</h3>
|
||||
{client.is_latest && <span className="badge-latest">最新</span>}
|
||||
{!client.is_active && <span className="badge-inactive">未启用</span>}
|
||||
<h3>{getPlatformLabel(client.platform_code)}</h3>
|
||||
{client.is_latest === true && <span className="badge-latest">最新</span>}
|
||||
{client.is_active === false && <span className="badge-inactive">未启用</span>}
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
|
|
@ -433,12 +511,6 @@ const ClientManagement = ({ user }) => {
|
|||
<span className="label">文件大小:</span>
|
||||
<span className="value">{formatFileSize(client.file_size)}</span>
|
||||
</div>
|
||||
{client.min_system_version && (
|
||||
<div className="info-row">
|
||||
<span className="label">系统要求:</span>
|
||||
<span className="value">{client.min_system_version}</span>
|
||||
</div>
|
||||
)}
|
||||
{client.release_notes && (
|
||||
<div className="release-notes">
|
||||
<div
|
||||
|
|
@ -490,39 +562,54 @@ const ClientManagement = ({ user }) => {
|
|||
{formData && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label><Monitor size={16} /> 平台类型 *</label>
|
||||
<div className="form-group" style={{ flex: 1 }}>
|
||||
<label><Monitor size={16} /> 选择平台 *</label>
|
||||
<select
|
||||
value={formData.platform_type}
|
||||
onChange={(e) => {
|
||||
const newType = e.target.value;
|
||||
handleInputChange('platform_type', newType);
|
||||
handleInputChange('platform_name', platformOptions[newType][0].value);
|
||||
}}
|
||||
value={formData.platform_code}
|
||||
onChange={(e) => handleInputChange('platform_code', e.target.value)}
|
||||
disabled={isEditing}
|
||||
>
|
||||
<option value="mobile">移动端</option>
|
||||
<option value="desktop">桌面端</option>
|
||||
<option value="terminal">专用终端</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label><Smartphone size={16} /> 具体平台 *</label>
|
||||
<select
|
||||
value={formData.platform_name}
|
||||
onChange={(e) => handleInputChange('platform_name', e.target.value)}
|
||||
disabled={isEditing}
|
||||
>
|
||||
{platformOptions[formData.platform_type].map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
<option value="">请选择平台</option>
|
||||
{platforms.tree.map(parentNode => (
|
||||
<optgroup key={parentNode.dict_code} label={parentNode.label_cn}>
|
||||
{parentNode.children && parentNode.children.map(childNode => (
|
||||
<option key={childNode.dict_code} value={childNode.dict_code}>
|
||||
{childNode.label_cn}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 文件上传区域 */}
|
||||
<div className="form-group">
|
||||
<label><Upload size={16} /> 上传安装包</label>
|
||||
<div className="upload-area">
|
||||
<input
|
||||
type="file"
|
||||
id="client-file-upload"
|
||||
accept=".apk,.exe,.dmg,.deb,.rpm,.pkg,.msi,.zip,.tar.gz"
|
||||
onChange={handleFileUpload}
|
||||
disabled={uploadingFile || !formData.platform_code}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
<label
|
||||
htmlFor="client-file-upload"
|
||||
className={`upload-label ${uploadingFile || !formData.platform_code ? 'disabled' : ''}`}
|
||||
>
|
||||
<Upload size={20} />
|
||||
<span>{uploadingFile ? '上传中...' : '选择文件'}</span>
|
||||
</label>
|
||||
<p className="upload-hint">
|
||||
{!formData.platform_code
|
||||
? '请先选择平台'
|
||||
: 'APK文件将自动读取版本信息,其他文件只读取文件大小'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<label><Package size={16} /> 版本号 *</label>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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: (
|
||||
<div className="tree-node-title">
|
||||
{node.parent_code === 'ROOT' ? <FolderTree size={14} /> : <FileText size={14} />}
|
||||
<span>{node.label_cn}</span>
|
||||
<span className="tree-node-code">({node.dict_code})</span>
|
||||
</div>
|
||||
),
|
||||
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 (
|
||||
<div className="dict-management">
|
||||
<div className="dict-header">
|
||||
<div className="dict-header-left">
|
||||
<BookText size={24} />
|
||||
<div>
|
||||
<h2>字典管理</h2>
|
||||
<p>管理系统中的码表数据(树形结构)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dict-main-layout">
|
||||
{/* 左侧面板 */}
|
||||
<div className="dict-left-panel">
|
||||
<Card
|
||||
title={
|
||||
<div className="panel-header">
|
||||
<FolderTree size={18} />
|
||||
<span>字典树</span>
|
||||
</div>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<Plus size={14} />}
|
||||
onClick={handleAddNode}
|
||||
>
|
||||
新增
|
||||
</Button>
|
||||
}
|
||||
bordered={false}
|
||||
className="dict-tree-card"
|
||||
>
|
||||
<div className="dict-type-selector">
|
||||
<label>字典类型:</label>
|
||||
<Select
|
||||
value={selectedDictType}
|
||||
onChange={setSelectedDictType}
|
||||
style={{ flex: 1 }}
|
||||
placeholder="选择字典类型"
|
||||
>
|
||||
{dictTypes.map(type => (
|
||||
<Option key={type} value={type}>{type}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="dict-tree-container">
|
||||
{treeData.length > 0 ? (
|
||||
<Tree
|
||||
showLine
|
||||
showIcon={false}
|
||||
treeData={treeData}
|
||||
onSelect={handleSelectNode}
|
||||
selectedKeys={selectedNode ? [selectedNode.dict_code] : []}
|
||||
defaultExpandAll
|
||||
/>
|
||||
) : (
|
||||
<Empty
|
||||
description="暂无数据"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 右侧面板 */}
|
||||
<div className="dict-right-panel">
|
||||
<Card
|
||||
title={
|
||||
<div className="panel-header">
|
||||
<FileText size={18} />
|
||||
<span>{selectedNode ? '编辑字典项' : isEditing ? '新增字典项' : '字典详情'}</span>
|
||||
</div>
|
||||
}
|
||||
extra={
|
||||
isEditing && (
|
||||
<Space>
|
||||
{selectedNode && (
|
||||
<Popconfirm
|
||||
title="确定要删除此项吗?"
|
||||
description="删除后将无法恢复"
|
||||
onConfirm={handleDelete}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
size="small"
|
||||
icon={<Trash2 size={14} />}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
<Button size="small" icon={<X size={14} />} onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<Save size={14} />}
|
||||
onClick={handleSave}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
bordered={false}
|
||||
className="dict-form-card"
|
||||
>
|
||||
{isEditing ? (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
dict_type: selectedDictType,
|
||||
parent_code: 'ROOT',
|
||||
sort_order: 0,
|
||||
status: 1,
|
||||
is_default: false
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="字典类型"
|
||||
name="dict_type"
|
||||
rules={[{ required: true, message: '请输入字典类型' }]}
|
||||
>
|
||||
<Input disabled={!!selectedNode} placeholder="如: client_platform" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="编码"
|
||||
name="dict_code"
|
||||
rules={[{ required: true, message: '请输入编码' }]}
|
||||
>
|
||||
<Input disabled={!!selectedNode} placeholder="如: WIN, MAC, ANDROID" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="中文名称"
|
||||
name="label_cn"
|
||||
rules={[{ required: true, message: '请输入中文名称' }]}
|
||||
>
|
||||
<Input placeholder="如: Windows" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="英文名称" name="label_en">
|
||||
<Input placeholder="如: Windows" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="父级编码"
|
||||
name="parent_code"
|
||||
rules={[{ required: true, message: '请选择父级' }]}
|
||||
>
|
||||
<Select placeholder="选择父级">
|
||||
{getParentOptions().map(opt => (
|
||||
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="排序" name="sort_order">
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="扩展属性(JSON格式)" name="extension_attr">
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder='如: {"suffix": ".exe", "arch_support": ["x86", "x64"]}'
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="form-inline-group">
|
||||
<div className="form-inline-item">
|
||||
<label>是否默认:</label>
|
||||
<Form.Item name="is_default" valuePropName="checked" noStyle>
|
||||
<Switch checkedChildren="是" unCheckedChildren="否" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div className="form-inline-item">
|
||||
<label>状态:</label>
|
||||
<Form.Item name="status" noStyle>
|
||||
<Select style={{ width: 120 }}>
|
||||
<Option value={1}>正常</Option>
|
||||
<Option value={0}>停用</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
) : (
|
||||
<Empty
|
||||
description="请从左侧树中选择一个节点进行编辑,或点击新增按钮创建新节点"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DictManagement;
|
||||
Loading…
Reference in New Issue