import React, { useState, useEffect } from 'react'; import { Card, Button, Input, Space, Drawer, Form, Select, Tag, message, Popconfirm, Typography, Divider, Tooltip, Row, Col, List, Empty, Skeleton, Switch, Modal, Pagination } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, SaveOutlined, StarFilled } from '@ant-design/icons'; import ReactMarkdown from 'react-markdown'; import { useDict } from '../../hooks/useDict'; import { getPromptPage, savePromptTemplate, updatePromptTemplate, deletePromptTemplate, updatePromptStatus, PromptTemplateVO, PromptTemplateDTO } from '../../api/business/prompt'; import { useTranslation } from 'react-i18next'; const { Option } = Select; const { Text, Title } = Typography; const PromptTemplates: React.FC = () => { const { t } = useTranslation(); const [form] = Form.useForm(); const [searchForm] = Form.useForm(); const { items: categories, loading: dictLoading } = useDict('biz_prompt_category'); const { items: dictTags } = useDict('biz_prompt_tag'); const { items: promptLevels } = useDict('biz_prompt_level'); const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [current, setCurrent] = useState(1); const [pageSize, setPageSize] = useState(12); const [drawerVisible, setDrawerVisible] = useState(false); const [editingId, setEditingId] = useState(null); const [submitLoading, setSubmitLoading] = useState(false); const [previewContent, setPreviewContent] = useState(''); const userProfile = React.useMemo(() => { const profileStr = sessionStorage.getItem("userProfile"); return profileStr ? JSON.parse(profileStr) : {}; }, []); const activeTenantId = React.useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []); const isPlatformAdmin = userProfile.isPlatformAdmin === true; const isTenantAdmin = userProfile.isTenantAdmin === true; useEffect(() => { fetchData(); }, [current, pageSize]); const fetchData = async () => { const values = searchForm.getFieldsValue(); setLoading(true); try { const res = await getPromptPage({ current, size: pageSize, name: values.name, category: values.category }); if (res.data && res.data.data) { setData(res.data.data.records); setTotal(res.data.data.total); } } catch (err) { console.error(err); } finally { setLoading(false); } }; const handleStatusChange = async (id: number, checked: boolean) => { try { await updatePromptStatus(id, checked ? 1 : 0); message.success(checked ? '模板已启用' : '模板已停用'); fetchData(); } catch (err) { console.error(err); } }; const handleOpenDrawer = (record?: PromptTemplateVO, isClone = false) => { if (record) { if (isClone) { setEditingId(null); form.setFieldsValue({ ...record, templateName: `${record.templateName} (副本)`, isSystem: 0, // 副本强制设为普通模板 id: undefined, tenantId: undefined }); setPreviewContent(record.promptContent); } else { const isPlatformLevel = Number(record.tenantId) === 0 && Number(record.isSystem) === 1; const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1; // 权限判定逻辑 let canEdit = false; if (Number(record.isSystem) === 0) { canEdit = Number(record.creatorId) === currentUserId; } else if (isPlatformAdmin) { canEdit = isPlatformLevel; } else if (isTenantAdmin) { canEdit = Number(record.tenantId) === activeTenantId; } else { canEdit = false; } if (!canEdit) { message.warning('您无权修改此层级的模板'); return; } setEditingId(record.id); form.setFieldsValue(record); setPreviewContent(record.promptContent); } } else { setEditingId(null); form.resetFields(); // 租户管理员或平台管理员新增默认选系统/租户预置 form.setFieldsValue({ status: 1, isSystem: (isTenantAdmin || isPlatformAdmin) ? 1 : 0 }); setPreviewContent(''); } setDrawerVisible(true); }; const showDetail = (record: PromptTemplateVO) => { Modal.info({ title: record.templateName, width: 800, icon: null, content: (
{record.promptContent}
), okText: '关闭', maskClosable: true }); }; const handleSubmit = async () => { try { const values = await form.validateFields(); setSubmitLoading(true); // 处理 tenantId,如果是新增且是平台管理员设为系统模板,手动设置 tenantId 为 0 if (!editingId && isPlatformAdmin && values.isSystem === 1) { values.tenantId = 0; } if (editingId) { await updatePromptTemplate({ ...values, id: editingId }); message.success('更新成功'); } else { await savePromptTemplate(values); message.success('模板已创建'); } setDrawerVisible(false); fetchData(); } catch (err) { console.error(err); } finally { setSubmitLoading(false); } }; const groupedData = React.useMemo(() => { const groups: Record = {}; data.forEach(item => { const cat = item.category || 'default'; if (!groups[cat]) groups[cat] = []; groups[cat].push(item); }); return groups; }, [data]); const renderCard = (item: PromptTemplateVO) => { const isSystem = item.isSystem === 1; const isPlatformLevel = Number(item.tenantId) === 0 && isSystem; const isTenantLevel = Number(item.tenantId) > 0 && isSystem; const isPersonalLevel = !isSystem; // 权限判定逻辑 (使用 Number 强制转换防止类型不匹配) let canEdit = false; const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1; if (isPersonalLevel) { // 个人模板仅本人可编辑 canEdit = Number(item.creatorId) === currentUserId; } else if (isPlatformAdmin) { // 平台管理员管理平台公开模板 (tenantId = 0) canEdit = Number(item.tenantId) === 0; } else if (isTenantAdmin) { // 租户管理员管理本租户公开模板 canEdit = Number(item.tenantId) === activeTenantId; } else { // 普通用户不可编辑公开模板 canEdit = false; } // 标签颜色与文字 const levelTag = isPlatformLevel ? ( 平台级 ) : isTenantLevel ? ( 租户级 ) : ( 个人级 ); return ( showDetail(item)} style={{ width: 320, borderRadius: 12, border: '1px solid #f0f0f0', position: 'relative', overflow: 'hidden' }} bodyStyle={{ padding: '24px' }} >
{levelTag}
e.stopPropagation()}> {canEdit && handleOpenDrawer(item)} />} handleStatusChange(item.id, checked)} disabled={false} />
{item.templateName} {/*使用次数: {item.usageCount || 0}*/}
{item.tags?.map(tag => { const dictItem = dictTags.find(dt => dt.itemValue === tag); return ( {dictItem ? dictItem.itemLabel : tag} ); })}
e.stopPropagation()}> handleOpenDrawer(item, true)} /> {canEdit && ( deletePromptTemplate(item.id).then(fetchData)} okText={t('common.confirm')} cancelText={t('common.cancel')} > )}
); }; return (
提示词模板
{Object.keys(groupedData).length === 0 ? ( ) : ( <> {Object.keys(groupedData).map(catKey => { const catLabel = categories.find(c => c.itemValue === catKey)?.itemLabel || catKey; return (
{catLabel}
{groupedData[catKey].map(renderCard)}
); })}
{ setCurrent(page); setPageSize(size); }} showTotal={(total) => `共 ${total} 条模板`} />
)}
{editingId ? '编辑模板' : '创建新模板'}} width="80%" onClose={() => setDrawerVisible(false)} open={drawerVisible} extra={ } destroyOnClose >
{(isPlatformAdmin || isTenantAdmin) && ( )} 提示词编辑器 (Markdown 实时预览) setPreviewContent(e.target.value)} style={{ height: '100%', fontFamily: 'monospace', resize: 'none', border: '1px solid #d9d9d9', borderRadius: 8, padding: 12 }} placeholder="在此输入 Markdown 指令..." />
{previewContent}
); }; export default PromptTemplates;