Update from Vibe Studio
This commit is contained in:
475
src/pages/certificate-management/index.tsx
Normal file
475
src/pages/certificate-management/index.tsx
Normal file
@@ -0,0 +1,475 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Button,
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
DatePicker,
|
||||
Select,
|
||||
Space,
|
||||
Tag,
|
||||
message,
|
||||
Popconfirm,
|
||||
Typography,
|
||||
Row,
|
||||
Col,
|
||||
Statistic,
|
||||
InputNumber
|
||||
} from 'antd'
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const { Title, Paragraph } = Typography
|
||||
const { Option } = Select
|
||||
const { RangePicker } = DatePicker
|
||||
|
||||
interface Certificate {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
vendor: string
|
||||
issueDate: dayjs.Dayjs
|
||||
expiryDate: dayjs.Dayjs
|
||||
status: 'active' | 'expired' | 'expiring_soon'
|
||||
description: string
|
||||
certificateNo: string
|
||||
}
|
||||
|
||||
const CertificateManagement: React.FC = () => {
|
||||
const [certificates, setCertificates] = useState<Certificate[]>([])
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [editingCertificate, setEditingCertificate] = useState<Certificate | null>(null)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [form] = Form.useForm()
|
||||
|
||||
// 模拟初始数据
|
||||
useEffect(() => {
|
||||
const initialData: Certificate[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Cisco CCNP Enterprise',
|
||||
type: '厂商认证',
|
||||
vendor: 'Cisco',
|
||||
issueDate: dayjs('2023-01-15'),
|
||||
expiryDate: dayjs('2026-01-15'),
|
||||
status: 'active',
|
||||
description: '思科网络专业认证',
|
||||
certificateNo: 'CSCO-2023-001234'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'AWS Solutions Architect',
|
||||
type: '云平台认证',
|
||||
vendor: 'Amazon',
|
||||
issueDate: dayjs('2023-06-01'),
|
||||
expiryDate: dayjs('2025-06-01'),
|
||||
status: 'expiring_soon',
|
||||
description: 'AWS解决方案架构师认证',
|
||||
certificateNo: 'AWS-2023-005678'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'VMware VCP-DCV',
|
||||
type: '虚拟化认证',
|
||||
vendor: 'VMware',
|
||||
issueDate: dayjs('2022-03-20'),
|
||||
expiryDate: dayjs('2024-03-20'),
|
||||
status: 'expired',
|
||||
description: 'VMware数据中心虚拟化认证',
|
||||
certificateNo: 'VMW-2022-009012'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Microsoft Azure Administrator',
|
||||
type: '云平台认证',
|
||||
vendor: 'Microsoft',
|
||||
issueDate: dayjs('2024-01-10'),
|
||||
expiryDate: dayjs('2027-01-10'),
|
||||
status: 'active',
|
||||
description: 'Azure管理员认证',
|
||||
certificateNo: 'MSFT-2024-003456'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'PMP项目管理专业人士',
|
||||
type: '项目管理认证',
|
||||
vendor: 'PMI',
|
||||
issueDate: dayjs('2023-08-15'),
|
||||
expiryDate: dayjs('2026-08-15'),
|
||||
status: 'active',
|
||||
description: '项目管理专业人士认证',
|
||||
certificateNo: 'PMI-2023-007890'
|
||||
}
|
||||
]
|
||||
setCertificates(initialData)
|
||||
}, [])
|
||||
|
||||
// 计算统计数据
|
||||
const stats = {
|
||||
total: certificates.length,
|
||||
active: certificates.filter(c => c.status === 'active').length,
|
||||
expiringSoon: certificates.filter(c => c.status === 'expiring_soon').length,
|
||||
expired: certificates.filter(c => c.status === 'expired').length
|
||||
}
|
||||
|
||||
// 获取状态标签
|
||||
const getStatusTag = (status: string) => {
|
||||
const config = {
|
||||
active: { color: 'green', text: '有效' },
|
||||
expiring_soon: { color: 'orange', text: '即将过期' },
|
||||
expired: { color: 'red', text: '已过期' }
|
||||
}
|
||||
const { color, text } = config[status as keyof typeof config]
|
||||
return <Tag color={color}>{text}</Tag>
|
||||
}
|
||||
|
||||
// 计算剩余天数
|
||||
const getDaysRemaining = (expiryDate: dayjs.Dayjs) => {
|
||||
const days = expiryDate.diff(dayjs(), 'day')
|
||||
return days
|
||||
}
|
||||
|
||||
// 打开新增/编辑弹窗
|
||||
const showModal = (certificate?: Certificate) => {
|
||||
if (certificate) {
|
||||
setEditingCertificate(certificate)
|
||||
form.setFieldsValue({
|
||||
...certificate,
|
||||
dateRange: [certificate.issueDate, certificate.expiryDate]
|
||||
})
|
||||
} else {
|
||||
setEditingCertificate(null)
|
||||
form.resetFields()
|
||||
}
|
||||
setIsModalVisible(true)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields()
|
||||
const [issueDate, expiryDate] = values.dateRange
|
||||
|
||||
const certificateData: Certificate = {
|
||||
id: editingCertificate?.id || Date.now().toString(),
|
||||
name: values.name,
|
||||
type: values.type,
|
||||
vendor: values.vendor,
|
||||
issueDate,
|
||||
expiryDate,
|
||||
description: values.description,
|
||||
certificateNo: values.certificateNo,
|
||||
status: calculateStatus(expiryDate)
|
||||
}
|
||||
|
||||
if (editingCertificate) {
|
||||
setCertificates(prev =>
|
||||
prev.map(c => c.id === editingCertificate.id ? certificateData : c)
|
||||
)
|
||||
message.success('证书更新成功')
|
||||
} else {
|
||||
setCertificates(prev => [...prev, certificateData])
|
||||
message.success('证书添加成功')
|
||||
}
|
||||
|
||||
setIsModalVisible(false)
|
||||
form.resetFields()
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算状态
|
||||
const calculateStatus = (expiryDate: dayjs.Dayjs): Certificate['status'] => {
|
||||
const days = expiryDate.diff(dayjs(), 'day')
|
||||
if (days < 0) return 'expired'
|
||||
if (days <= 90) return 'expiring_soon'
|
||||
return 'active'
|
||||
}
|
||||
|
||||
// 删除证书
|
||||
const handleDelete = (id: string) => {
|
||||
setCertificates(prev => prev.filter(c => c.id !== id))
|
||||
message.success('证书删除成功')
|
||||
}
|
||||
|
||||
// 搜索过滤
|
||||
const filteredCertificates = certificates.filter(cert =>
|
||||
cert.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
cert.vendor.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
cert.certificateNo.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '证书名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string) => <strong>{text}</strong>
|
||||
},
|
||||
{
|
||||
title: '证书编号',
|
||||
dataIndex: 'certificateNo',
|
||||
key: 'certificateNo',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 120,
|
||||
render: (type: string) => (
|
||||
<Tag color='blue'>{type}</Tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '厂商',
|
||||
dataIndex: 'vendor',
|
||||
key: 'vendor',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '颁发日期',
|
||||
dataIndex: 'issueDate',
|
||||
key: 'issueDate',
|
||||
width: 120,
|
||||
render: (date: dayjs.Dayjs) => date.format('YYYY-MM-DD')
|
||||
},
|
||||
{
|
||||
title: '到期日期',
|
||||
dataIndex: 'expiryDate',
|
||||
key: 'expiryDate',
|
||||
width: 120,
|
||||
render: (date: dayjs.Dayjs) => {
|
||||
const days = getDaysRemaining(date)
|
||||
return (
|
||||
<Space>
|
||||
<span>{date.format('YYYY-MM-DD')}</span>
|
||||
{days > 0 && days <= 90 && (
|
||||
<Tag color='orange'>{days}天</Tag>
|
||||
)}
|
||||
{days <= 0 && (
|
||||
<Tag color='red'>已过期</Tag>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (status: string) => getStatusTag(status)
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150,
|
||||
render: (_: unknown, record: Certificate) => (
|
||||
<Space>
|
||||
<Button
|
||||
type='text'
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => showModal(record)}
|
||||
/>
|
||||
<Popconfirm
|
||||
title='确定要删除此证书吗?'
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
okText='确认'
|
||||
cancelText='取消'
|
||||
>
|
||||
<Button type='text' danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Title level={2}>
|
||||
<SafetyCertificateOutlined style={{ marginRight: 8 }} />
|
||||
证书管理系统
|
||||
</Title>
|
||||
<Paragraph type='secondary'>
|
||||
管理您的系统集成商证书,跟踪有效期并及时续期
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='证书总数'
|
||||
value={stats.total}
|
||||
prefix={<SafetyCertificateOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='有效证书'
|
||||
value={stats.active}
|
||||
valueStyle={{ color: '#3f8600' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='即将过期'
|
||||
value={stats.expiringSoon}
|
||||
valueStyle={{ color: '#faad14' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title='已过期'
|
||||
value={stats.expired}
|
||||
valueStyle={{ color: '#cf1322' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 证书表格 */}
|
||||
<Card
|
||||
title='证书列表'
|
||||
extra={
|
||||
<Space>
|
||||
<Input
|
||||
placeholder='搜索证书名称、厂商或编号'
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{ width: 250 }}
|
||||
allowClear
|
||||
/>
|
||||
<Button
|
||||
type='primary'
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => showModal()}
|
||||
>
|
||||
添加证书
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredCertificates}
|
||||
rowKey='id'
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 新增/编辑弹窗 */}
|
||||
<Modal
|
||||
title={editingCertificate ? '编辑证书' : '添加证书'}
|
||||
open={isModalVisible}
|
||||
onOk={handleSubmit}
|
||||
onCancel={() => {
|
||||
setIsModalVisible(false)
|
||||
form.resetFields()
|
||||
}}
|
||||
width={600}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout='vertical'
|
||||
initialValues={{ type: '厂商认证', vendor: 'Cisco' }}
|
||||
>
|
||||
<Form.Item
|
||||
name='name'
|
||||
label='证书名称'
|
||||
rules={[{ required: true, message: '请输入证书名称' }]}
|
||||
>
|
||||
<Input placeholder='请输入证书名称' />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name='certificateNo'
|
||||
label='证书编号'
|
||||
rules={[{ required: true, message: '请输入证书编号' }]}
|
||||
>
|
||||
<Input placeholder='请输入证书编号' />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name='type'
|
||||
label='证书类型'
|
||||
rules={[{ required: true, message: '请选择证书类型' }]}
|
||||
>
|
||||
<Select placeholder='请选择证书类型'>
|
||||
<Option value='厂商认证'>厂商认证</Option>
|
||||
<Option value='云平台认证'>云平台认证</Option>
|
||||
<Option value='虚拟化认证'>虚拟化认证</Option>
|
||||
<Option value='项目管理认证'>项目管理认证</Option>
|
||||
<Option value='安全认证'>安全认证</Option>
|
||||
<Option value='其他认证'>其他认证</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name='vendor'
|
||||
label='厂商/颁发机构'
|
||||
rules={[{ required: true, message: '请输入厂商或颁发机构' }]}
|
||||
>
|
||||
<Input placeholder='请输入厂商或颁发机构名称' />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name='dateRange'
|
||||
label='有效期限'
|
||||
rules={[{ required: true, message: '请选择有效期限' }]}
|
||||
>
|
||||
<RangePicker style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name='description'
|
||||
label='备注说明'
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
placeholder='请输入备注说明(可选)'
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CertificateManagement
|
||||
@@ -30,7 +30,7 @@ const router: RouteObject[] = [
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: LazyLoad(lazy(() => import('@/pages/home')))
|
||||
element: LazyLoad(lazy(() => import('@/pages/certificate-management')))
|
||||
},
|
||||
{
|
||||
path: '/test1',
|
||||
|
||||
Reference in New Issue
Block a user