Update from Vibe Studio

This commit is contained in:
Vibe Studio
2026-01-28 10:43:37 +00:00
parent 976704f9ec
commit 612817588e
2 changed files with 476 additions and 1 deletions

View 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

View File

@@ -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',