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: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: LazyLoad(lazy(() => import('@/pages/home')))
|
element: LazyLoad(lazy(() => import('@/pages/certificate-management')))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/test1',
|
path: '/test1',
|
||||||
|
|||||||
Reference in New Issue
Block a user