diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..b97adc551ec322c84e018c01880d3a1bbb0dc4a2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + 提示词管理应用 + + + +
+ + + \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2f0cb00c6ebbeb58d1e1b3bbe960c2e140cd62b3 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { AuthProvider } from './contexts/AuthContext'; +import { AppProvider } from './contexts/AppContext'; +import ProtectedRoute from './components/ProtectedRoute'; +import LoginPage from './pages/LoginPage'; +import HomePage from './pages/HomePage'; +import PromptGroupDetailPage from './pages/PromptGroupDetailPage'; +import CreatePromptGroupPage from './pages/CreatePromptGroupPage'; +import EditPromptGroupPage from './pages/EditPromptGroupPage'; +import CategoriesPage from './pages/CategoriesPage'; +import SettingsPage from './pages/SettingsPage'; +import './styles/global.css'; +import './styles/iosStyles.css'; + +function App() { + return ( + + + + + {/* 公开路由 */} + } /> + + {/* 受保护路由 */} + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + {/* 默认重定向 */} + } /> + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/src/components/Category/CategoryBadge.tsx b/src/components/Category/CategoryBadge.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b9a252594fb9cd50752398dc99d32adc6a593abc --- /dev/null +++ b/src/components/Category/CategoryBadge.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Category } from '../../types'; + +interface CategoryBadgeProps { + category: Category; + onClick?: () => void; + className?: string; +} + +const CategoryBadge: React.FC = ({ + category, + onClick, + className = '' +}) => { + return ( +
+ {category.name} +
+ ); +}; + +export default CategoryBadge; \ No newline at end of file diff --git a/src/components/Category/CategoryForm.tsx b/src/components/Category/CategoryForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c2328a26f5cb14bb36f7af37ba66df8d2162bf92 --- /dev/null +++ b/src/components/Category/CategoryForm.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import Input from '../common/Input'; +import Button from '../common/Button'; +import { Category } from '../../types'; + +interface CategoryFormProps { + initialCategory?: Partial; + onSubmit: (category: Omit) => void; + onCancel: () => void; +} + +const CategoryForm: React.FC = ({ + initialCategory = {}, + onSubmit, + onCancel +}) => { + const [name, setName] = useState(initialCategory.name || ''); + const [color, setColor] = useState(initialCategory.color || '#007AFF'); + const [error, setError] = useState(''); + + const colorOptions = [ + { color: '#007AFF', name: '蓝色' }, + { color: '#4CD964', name: '绿色' }, + { color: '#FF3B30', name: '红色' }, + { color: '#FF9500', name: '橙色' }, + { color: '#FFCC00', name: '黄色' }, + { color: '#5856D6', name: '紫色' }, + { color: '#FF2D55', name: '粉色' }, + { color: '#5AC8FA', name: '浅蓝色' }, + ]; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!name.trim()) { + setError('请输入分类名称'); + return; + } + + onSubmit({ + name: name.trim(), + color + }); + }; + + + return ( +
+ { + setName(e.target.value); + if (error) setError(''); + }} + error={error} + required + /> + +
+ +
+ {colorOptions.map((option) => ( +
setColor(option.color)} + title={option.name} + /> + ))} +
+
+ +
+ + +
+ + ); +}; + +export default CategoryForm; \ No newline at end of file diff --git a/src/components/Category/CategorySelector.tsx b/src/components/Category/CategorySelector.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d3d6950b12c29f1f93704d9bc0c40efd043e1705 --- /dev/null +++ b/src/components/Category/CategorySelector.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import { Category } from '../../types'; +import { useApp } from '../../contexts/AppContext'; +import CategoryBadge from './CategoryBadge'; + +interface CategorySelectorProps { + selectedCategory: string | Category; + onChange: (categoryId: string) => void; + className?: string; +} + + +const CategorySelector: React.FC = ({ + selectedCategory, + onChange, + className = '' +}) => { + const { categories } = useApp(); + const [showDropdown, setShowDropdown] = useState(false); + + const selectedCategoryObj = categories.find(c => c._id === selectedCategory); + + const handleCategorySelect = (categoryId: string) => { + onChange(categoryId); + setShowDropdown(false); + }; + + return ( +
+
setShowDropdown(!showDropdown)} + > + {selectedCategoryObj ? ( + + ) : ( +
+ 选择分类 +
+ )} + + + +
+ + {showDropdown && ( +
+
+ {categories.map((category) => ( +
handleCategorySelect(category._id)} + > +
+ {category.name} + {selectedCategory === category._id && ( + + + + )} +
+ ))} +
+
+ )} +
+ ); +}; + +export default CategorySelector; \ No newline at end of file diff --git a/src/components/DslFile/DslFileList.tsx b/src/components/DslFile/DslFileList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cd1624f5293b612e8e390e8548ee98d565755bce --- /dev/null +++ b/src/components/DslFile/DslFileList.tsx @@ -0,0 +1,275 @@ +import React, { useState } from 'react'; +import { DslFile } from '../../types'; +import { useApp } from '../../contexts/AppContext'; +import Button from '../common/Button'; +import TextArea from '../common/TextArea'; +import Input from '../common/Input'; +import Modal from '../common/Modal'; + +interface DslFileListProps { + groupId: string; + files: DslFile[]; +} + +const DslFileList: React.FC = ({ groupId, files }) => { + const { deleteDslFile, updateDslFile } = useApp(); + const [selectedFile, setSelectedFile] = useState(null); + const [showViewModal, setShowViewModal] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + const [editFileName, setEditFileName] = useState(''); + const [editContent, setEditContent] = useState(''); + const [error, setError] = useState(''); + + if (files.length === 0) { + return ( +
+
+ + + + + + + +
+

暂无 YAML 文件

+

上传或添加 YAML 文件以便于存储和管理

+
+ ); + } + + const formatDate = (date: string | Date) => { + const dateObj = typeof date === 'string' ? new Date(date) : date; + return dateObj.toLocaleDateString('zh-CN', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + const handleDelete = (fileId: string) => { + if (window.confirm('确定要删除此 YAML 文件吗?此操作不可撤销。')) { + deleteDslFile(groupId, fileId); + } + }; + + const handleViewFile = (file: DslFile) => { + setSelectedFile(file); + setShowViewModal(true); + }; + + const handleEditFile = (file: DslFile) => { + setSelectedFile(file); + setEditFileName(file.name); + setEditContent(file.content); + setShowEditModal(true); + }; + + const handleSaveEdit = async () => { + if (!selectedFile) return; + setError(''); + + if (!editFileName.trim()) { + setError('请输入文件名'); + return; + } + + if (!editContent.trim()) { + setError('请输入YAML内容'); + return; + } + + try { + await updateDslFile(groupId, selectedFile._id, { + name: editFileName, + content: editContent + }); + setShowEditModal(false); + } catch (err: any) { + console.error('更新YAML文件失败:', err); + setError(err.message || '更新失败,请重试'); + } + }; + + const handleExportFile = (file: DslFile) => { + // 创建 Blob 对象 + const blob = new Blob([file.content], { type: 'text/yaml' }); + + // 创建下载链接 + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = file.name; + document.body.appendChild(a); + a.click(); + + // 清理 + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + }; + + const getFileIcon = (fileName: string) => { + return ( + + + + + + + + + ); + }; + + return ( +
+ {files.map((file) => ( +
+
+ {getFileIcon(file.name)} +
+
+
{file.name}
+
+ 上传于 {formatDate(file.uploadedAt)} +
+
+
+ + + + +
+
+ ))} + + {/* 查看文件模态框 */} + setShowViewModal(false)} + title={selectedFile?.name || "查看 YAML 文件"} + > +
+ {selectedFile && ( +
+
+
+                  {selectedFile.content}
+                
+
+ +
+ + +
+
+ )} +
+
+ + {/* 编辑文件模态框 */} + setShowEditModal(false)} + title="编辑 YAML 文件" + > +
+ {error && ( +
+ {error} +
+ )} + + setEditFileName(e.target.value)} + required + /> + +