diff --git a/.DS_Store b/.DS_Store index b1b4307..75641e9 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/dist.zip b/dist.zip index 44dca17..a8ea53e 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 85c30b4..dac39da 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -4,7 +4,7 @@ import './Header.css'; const Header = () => { return (
-

iMeeting (智慧会议)

+

iMeeting (慧会议)

); }; diff --git a/src/components/admin/PromptManagement.css b/src/components/admin/PromptManagement.css new file mode 100644 index 0000000..a684cf1 --- /dev/null +++ b/src/components/admin/PromptManagement.css @@ -0,0 +1,196 @@ +.prompt-management { + padding: 1rem; +} + +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.toolbar h2 { + font-size: 1.5rem; + font-weight: 600; +} + +.prompt-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.prompt-card { + background: #fff; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 1.5rem; + display: flex; + flex-direction: column; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + transition: box-shadow 0.3s ease, transform 0.3s ease; +} + +.prompt-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(0,0,0,0.1); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.75rem; +} + +.card-header h3 { + font-size: 1.1rem; + font-weight: 600; + margin: 0; + line-height: 1.3; +} + +.card-actions { + display: flex; + gap: 0.5rem; + position: relative; /* For dropdown positioning */ +} + +.dropdown-trigger { + background: none; + border: none; + cursor: pointer; + color: #6c757d; + padding: 0.25rem; + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.dropdown-trigger:hover { + background-color: #f1f3f5; + color: #212529; +} + +.dropdown-menu { + position: absolute; + top: 100%; + right: 0; + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border: 1px solid #e2e8f0; + padding: 0.5rem; + margin-top: 0.5rem; + min-width: 120px; + z-index: 10; + display: flex; + flex-direction: column; + gap: 2px; +} + +.dropdown-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + border: none; + background: none; + cursor: pointer; + border-radius: 4px; + font-size: 0.875rem; + font-weight: 500; + color: #374151; + text-decoration: none; + transition: all 0.2s ease; + width: 100%; + text-align: left; +} + +.dropdown-item:hover { + background: #f3f4f6; + color: #111827; +} + +.dropdown-item.delete-item { + color: #dc2626; +} + +.dropdown-item.delete-item:hover { + background: #fef2f2; + color: #b91c1c; +} + +.card-actions button { + background: none; + border: none; + cursor: pointer; + color: #6c757d; + padding: 0.25rem; + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.card-actions button:hover { + background-color: #f1f3f5; + color: #212529; +} + +.card-tags { + margin-bottom: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.tag-chip { + background-color: #e9ecef; + color: #f2f0f7; + padding: 0.25rem 0.6rem; + border-radius: 12px; + font-size: 0.75rem; +} + +.card-content { + font-size: 0.9rem; + color: #495057; + line-height: 1.5; + flex-grow: 1; +} + +/* Reusing existing modal and form styles */ +/* Ensure these are defined globally or copy them here if needed */ +.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(5px); } +.modal-content { background: white; color: #333; padding: 2rem; border-radius: 12px; width: 90%; max-width: 600px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } +.modal-content h2 { margin-top: 0; margin-bottom: 1.5rem; } +.modal-actions { display: flex; justify-content: flex-end; gap: 1rem; margin-top: 1.5rem; } +.form-group { margin-bottom: 1rem; } +.form-group label { display: flex; align-items: center; gap: 0.5rem; font-weight: 500; margin-bottom: 0.5rem; } +.form-group input[type="text"], .form-group textarea { width: 100%; padding: 0.75rem; border: 1px solid #ced4da; border-radius: 6px; font-size: 1rem; } +.form-group textarea { resize: vertical; } + +.pagination { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 2rem; + padding-top: 1rem; + border-top: 1px solid #e9ecef; +} + +.pagination button { + padding: 0.5rem 1rem; + border: 1px solid #ced4da; + background-color: #fff; + border-radius: 6px; + cursor: pointer; +} + +.pagination button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.pagination span { + font-size: 0.9rem; + color: #495057; +} diff --git a/src/components/admin/PromptManagement.jsx b/src/components/admin/PromptManagement.jsx new file mode 100644 index 0000000..59974d0 --- /dev/null +++ b/src/components/admin/PromptManagement.jsx @@ -0,0 +1,176 @@ +import React, { useState, useEffect, useRef } from 'react'; +import apiClient from '../../utils/apiClient'; +import { buildApiUrl, API_ENDPOINTS } from '../../config/api'; +import { Plus, MoreVertical, Edit, Trash2, BookText, Tag, FileText } from 'lucide-react'; +import './PromptManagement.css'; +import TagEditor from '../TagEditor'; // Reusing the TagEditor component + +const PromptManagement = () => { + const [prompts, setPrompts] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(12); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [showModal, setShowModal] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [currentPrompt, setCurrentPrompt] = useState(null); + const [activeMenu, setActiveMenu] = useState(null); // For dropdown menu + const menuRef = useRef(null); + + useEffect(() => { + fetchPrompts(); + }, [page, pageSize]); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event) => { + if (menuRef.current && !menuRef.current.contains(event.target)) { + setActiveMenu(null); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + const fetchPrompts = async () => { + setLoading(true); + try { + const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.PROMPTS.LIST}?page=${page}&size=${pageSize}`)); + setPrompts(response.data.prompts); + setTotal(response.data.total); + } catch (err) { + setError(err.response?.data?.message || '无法加载提示词列表'); + } finally { + setLoading(false); + } + }; + + const handleOpenModal = (prompt = null) => { + if (prompt) { + setIsEditing(true); + setCurrentPrompt({ ...prompt }); + } else { + setIsEditing(false); + setCurrentPrompt({ name: '', tags: '', content: '' }); + } + setError(''); + setShowModal(true); + setActiveMenu(null); // Close menu when modal opens + }; + + const handleCloseModal = () => { + setShowModal(false); + setCurrentPrompt(null); + }; + + const handleSave = async () => { + try { + if (isEditing) { + await apiClient.put(buildApiUrl(API_ENDPOINTS.PROMPTS.UPDATE(currentPrompt.id)), currentPrompt); + } else { + await apiClient.post(buildApiUrl(API_ENDPOINTS.PROMPTS.CREATE), currentPrompt); + } + handleCloseModal(); + fetchPrompts(); // Refresh list + } catch (err) { + setError(err.response?.data?.message || '保存失败'); + } + }; + + const handleDelete = async (promptId) => { + setActiveMenu(null); // Close menu + if (window.confirm('您确定要删除这个提示词吗?')) { + try { + await apiClient.delete(buildApiUrl(API_ENDPOINTS.PROMPTS.DELETE(promptId))); + fetchPrompts(); // Refresh list + } catch (err) { + setError(err.response?.data?.message || '删除失败'); + } + } + }; + + const handleInputChange = (field, value) => { + setCurrentPrompt(prev => ({ ...prev, [field]: value })); + }; + + const toggleMenu = (promptId) => { + setActiveMenu(activeMenu === promptId ? null : promptId); + }; + + return ( +
+
+

提示词仓库

+ +
+ {loading &&

加载中...

} + {error && !showModal &&

{error}

} + {!loading && ( + <> +
+ {prompts.map(prompt => ( +
+
+

{prompt.name}

+
+ + {activeMenu === prompt.id && ( +
+ + +
+ )} +
+
+
+ {prompt.tags && prompt.tags.split(',').map(t => t.trim()).filter(t => t).map((tag, i) => ( + {tag} + ))} +
+

{prompt.content.substring(0, 100)}{prompt.content.length > 100 && '...'}

+
+ ))} +
+
+ 总计 {total} 条 +
+ + 第 {page} 页 / {Math.ceil(total / pageSize)} + +
+
+ + )} + + {showModal && ( +
+
+

{isEditing ? '编辑提示词' : '新增提示词'}

+ {error &&
{error}
} +
+ + handleInputChange('name', e.target.value)} /> +
+
+ + handleInputChange('tags', value)} /> +
+
+ +