From 5c262d2f458626c4b3af1439da14f408b3e59800 Mon Sep 17 00:00:00 2001 From: Chever John Date: Tue, 26 Aug 2025 21:08:51 +0800 Subject: [PATCH] Initializes project with core setup Sets up the project structure, including essential configuration files (eslint, postcss, tailwind), initial React components, and core UI component library (shadcn/ui). Includes necessary dependencies and scripts to start development, build, and lint the project. Ensures correct setup of React Router and a basic App structure. Does this address a real problem or an imagined one? The initail setup addresses the real problem of setting up all the base structure for the project, so it is a real problem. Is there a simpler way to do this? The steps being taken are more or less industry standard for initializing a new project with these tools. What will break? None of these changes will break anything; this is a new project setup. --- components.json | 21 + eslint.config.js | 28 + index.html | 56 ++ package.json | 84 +++ postcss.config.js | 6 + src/App.tsx | 21 + src/components/AdminPanel.tsx | 649 ++++++++++++++++++++ src/components/ui/accordion.tsx | 55 ++ src/components/ui/alert-dialog.tsx | 139 +++++ src/components/ui/alert.tsx | 59 ++ src/components/ui/aspect-ratio.tsx | 5 + src/components/ui/avatar.tsx | 48 ++ src/components/ui/badge.tsx | 36 ++ src/components/ui/breadcrumb.tsx | 115 ++++ src/components/ui/button.tsx | 57 ++ src/components/ui/calendar.tsx | 74 +++ src/components/ui/card.tsx | 76 +++ src/components/ui/carousel.tsx | 260 ++++++++ src/components/ui/chart.tsx | 363 +++++++++++ src/components/ui/checkbox.tsx | 28 + src/components/ui/collapsible.tsx | 9 + src/components/ui/command.tsx | 151 +++++ src/components/ui/context-menu.tsx | 198 ++++++ src/components/ui/dialog.tsx | 122 ++++ src/components/ui/drawer.tsx | 116 ++++ src/components/ui/dropdown-menu.tsx | 199 ++++++ src/components/ui/form.tsx | 176 ++++++ src/components/ui/hover-card.tsx | 27 + src/components/ui/input-otp.tsx | 69 +++ src/components/ui/input.tsx | 22 + src/components/ui/label.tsx | 26 + src/components/ui/menubar.tsx | 254 ++++++++ src/components/ui/navigation-menu.tsx | 128 ++++ src/components/ui/pagination.tsx | 117 ++++ src/components/ui/popover.tsx | 31 + src/components/ui/progress.tsx | 26 + src/components/ui/radio-group.tsx | 42 ++ src/components/ui/resizable.tsx | 43 ++ src/components/ui/scroll-area.tsx | 46 ++ src/components/ui/select.tsx | 157 +++++ src/components/ui/separator.tsx | 29 + src/components/ui/sheet.tsx | 138 +++++ src/components/ui/sidebar.tsx | 771 +++++++++++++++++++++++ src/components/ui/skeleton.tsx | 15 + src/components/ui/slider.tsx | 26 + src/components/ui/sonner.tsx | 29 + src/components/ui/switch.tsx | 27 + src/components/ui/table.tsx | 120 ++++ src/components/ui/tabs.tsx | 53 ++ src/components/ui/textarea.tsx | 22 + src/components/ui/toast.tsx | 127 ++++ src/components/ui/toaster.tsx | 33 + src/components/ui/toggle-group.tsx | 59 ++ src/components/ui/toggle.tsx | 43 ++ src/components/ui/tooltip.tsx | 32 + src/data/jobs.ts | 176 ++++++ src/hooks/use-mobile.tsx | 24 + src/hooks/use-toast.ts | 192 ++++++ src/index.css | 154 +++++ src/lib/dataManager.ts | 216 +++++++ src/lib/security.ts | 123 ++++ src/lib/utils.ts | 6 + src/main.tsx | 10 + src/pages/HomePage.tsx | 852 ++++++++++++++++++++++++++ src/pages/NotFoundPage.tsx | 20 + src/vite-env.d.ts | 1 + tailwind.config.js | 99 +++ tsconfig.app.json | 31 + tsconfig.json | 19 + tsconfig.node.json | 24 + vite.config.ts | 13 + 71 files changed, 7623 insertions(+) create mode 100644 components.json create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 src/App.tsx create mode 100644 src/components/AdminPanel.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/chart.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toast.tsx create mode 100644 src/components/ui/toaster.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/data/jobs.ts create mode 100644 src/hooks/use-mobile.tsx create mode 100644 src/hooks/use-toast.ts create mode 100644 src/index.css create mode 100644 src/lib/dataManager.ts create mode 100644 src/lib/security.ts create mode 100644 src/lib/utils.ts create mode 100644 src/main.tsx create mode 100644 src/pages/HomePage.tsx create mode 100644 src/pages/NotFoundPage.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..1d282e6 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/index.html b/index.html new file mode 100644 index 0000000..da21422 --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + + + + + + MiniMax AI Infra/算法 团队 + + + + + + +
+ + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..bb81d70 --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "vite-project", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@devvai/devv-code-backend": "^1.0.0", + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-accordion": "^1.2.8", + "@radix-ui/react-alert-dialog": "^1.1.11", + "@radix-ui/react-aspect-ratio": "^1.1.4", + "@radix-ui/react-avatar": "^1.1.7", + "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-collapsible": "^1.1.8", + "@radix-ui/react-context-menu": "^2.2.12", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-dropdown-menu": "^2.1.12", + "@radix-ui/react-hover-card": "^1.1.11", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-menubar": "^1.1.12", + "@radix-ui/react-navigation-menu": "^1.2.10", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-progress": "^1.1.4", + "@radix-ui/react-radio-group": "^1.3.4", + "@radix-ui/react-scroll-area": "^1.2.6", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-slider": "^1.3.2", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-switch": "^1.2.2", + "@radix-ui/react-tabs": "^1.1.9", + "@radix-ui/react-toast": "^1.2.11", + "@radix-ui/react-toggle": "^1.1.6", + "@radix-ui/react-toggle-group": "^1.1.7", + "@radix-ui/react-tooltip": "^1.2.4", + "@tanstack/react-table": "^8.21.3", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.503.0", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-day-picker": "8.10.1", + "react-dom": "^18.2.0", + "react-hook-form": "^7.56.1", + "react-resizable-panels": "^2.1.8", + "react-router-dom": "^6.22.1", + "recharts": "^2.15.3", + "sonner": "^2.0.3", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", + "tw-animate-css": "^1.2.8", + "vaul": "^1.1.2", + "xlsx": "^0.18.5", + "zod": "^3.24.3", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.22.0", + "@types/node": "^22.14.1", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.21", + "eslint": "^9.22.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "postcss": "^8.5.3", + "tailwindcss": "3", + "typescript": "~5.7.2", + "typescript-eslint": "^8.26.1", + "vite": "^6.3.1" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..72eb480 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,21 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import HomePage from "@/pages/HomePage"; +import NotFoundPage from "@/pages/NotFoundPage"; +import { Toaster } from "@/components/ui/toaster"; +import { TooltipProvider } from "@/components/ui/tooltip"; + +function App() { + return ( + + + + } /> + } /> + + + + + ); +} + +export default App; diff --git a/src/components/AdminPanel.tsx b/src/components/AdminPanel.tsx new file mode 100644 index 0000000..7dff5bc --- /dev/null +++ b/src/components/AdminPanel.tsx @@ -0,0 +1,649 @@ +import React, { useState, useRef } from 'react'; +import { JobPosition } from '@/data/jobs'; +import { JobDataManager } from '@/lib/dataManager'; +import { SecurityManager, validateAccessCode } from '@/lib/security'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { useToast } from '@/hooks/use-toast'; +import { Shield, Upload, Download, History, Plus, Edit2, Trash2, X, AlertTriangle, FileText, FileSpreadsheet } from 'lucide-react'; + +interface AdminPanelProps { + isOpen: boolean; + onClose: () => void; + jobs: JobPosition[]; + onJobsUpdate: (jobs: JobPosition[]) => void; +} + +export function AdminPanel({ isOpen, onClose, jobs, onJobsUpdate }: AdminPanelProps) { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [accessCode, setAccessCode] = useState(''); + const [isValidating, setIsValidating] = useState(false); + const [editingJob, setEditingJob] = useState(null); + const [isJobDialogOpen, setIsJobDialogOpen] = useState(false); + const [showDeleteAllDialog, setShowDeleteAllDialog] = useState(false); + const [deleteAllConfirmText, setDeleteAllConfirmText] = useState(''); + const fileInputRef = useRef(null); + const { toast } = useToast(); + + // Handle authentication + const handleLogin = async () => { + if (SecurityManager.isLocked()) { + const remainingTime = Math.ceil(SecurityManager.getRemainingLockTime() / 60000); + toast({ + title: "访问被锁定", + description: `请 ${remainingTime} 分钟后再试`, + variant: "destructive" + }); + return; + } + + setIsValidating(true); + try { + const isValid = await validateAccessCode(accessCode); + if (isValid) { + setIsAuthenticated(true); + SecurityManager.resetAttempts(); + toast({ + title: "认证成功", + description: "欢迎进入管理面板" + }); + } else { + SecurityManager.recordFailedAttempt(); + const attempts = SecurityManager.getFailedAttempts(); + toast({ + title: "访问码错误", + description: `剩余尝试次数: ${3 - attempts}`, + variant: "destructive" + }); + } + } catch (error) { + toast({ + title: "认证失败", + description: "系统错误,请重试", + variant: "destructive" + }); + } + setIsValidating(false); + setAccessCode(''); + }; + + // Handle file import + const handleFileImport = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + try { + let importedJobs: JobPosition[]; + + if (file.name.endsWith('.json')) { + importedJobs = await JobDataManager.importFromJSON(file); + } else if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) { + importedJobs = await JobDataManager.importFromXLSX(file); + } else { + throw new Error('不支持的文件格式,请使用 JSON 或 Excel 文件'); + } + + const updatedJobs = [...jobs, ...importedJobs]; + onJobsUpdate(updatedJobs); + JobDataManager.saveJobs(updatedJobs); + + toast({ + title: "导入成功", + description: `成功导入 ${importedJobs.length} 个职位` + }); + } catch (error) { + toast({ + title: "导入失败", + description: error.message, + variant: "destructive" + }); + } + + // Reset file input + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + // Handle job operations + const handleAddJob = () => { + setEditingJob({ + id: `job_${Date.now()}`, + title: '', + department: '', + priority: 'normal', + url: '', + description: [], + requirements: [] + }); + setIsJobDialogOpen(true); + }; + + const handleEditJob = (job: JobPosition) => { + setEditingJob({ ...job }); + setIsJobDialogOpen(true); + }; + + const handleDeleteJob = (jobId: string) => { + const updatedJobs = jobs.filter(job => job.id !== jobId); + onJobsUpdate(updatedJobs); + JobDataManager.saveJobs(updatedJobs); + toast({ + title: "删除成功", + description: "职位已删除" + }); + }; + + const handleDeleteAllJobs = () => { + if (deleteAllConfirmText !== 'DELETE ALL') { + toast({ + title: "确认文本错误", + description: "请输入 'DELETE ALL' 来确认删除", + variant: "destructive" + }); + return; + } + + // Save current jobs to history before deleting all + JobDataManager.saveJobs(jobs); + + // Clear all jobs + const emptyJobs: JobPosition[] = []; + onJobsUpdate(emptyJobs); + JobDataManager.saveJobs(emptyJobs); + + setShowDeleteAllDialog(false); + setDeleteAllConfirmText(''); + + toast({ + title: "删除成功", + description: `已删除所有 ${jobs.length} 个职位`, + variant: "destructive" + }); + }; + + const handleSaveJob = (job: JobPosition) => { + const existingIndex = jobs.findIndex(j => j.id === job.id); + let updatedJobs: JobPosition[]; + + if (existingIndex >= 0) { + updatedJobs = [...jobs]; + updatedJobs[existingIndex] = job; + } else { + updatedJobs = [...jobs, job]; + } + + onJobsUpdate(updatedJobs); + JobDataManager.saveJobs(updatedJobs); + setIsJobDialogOpen(false); + setEditingJob(null); + + toast({ + title: existingIndex >= 0 ? "更新成功" : "添加成功", + description: "职位信息已保存" + }); + }; + + // Handle history restoration + const handleRestoreHistory = (timestamp: number) => { + const restoredJobs = JobDataManager.restoreFromHistory(timestamp); + if (restoredJobs) { + onJobsUpdate(restoredJobs); + JobDataManager.saveJobs(restoredJobs); + toast({ + title: "恢复成功", + description: "数据已恢复到选定版本" + }); + } + }; + + if (!isOpen) return null; + + return ( + onClose()}> + + + + + MiniMax AI Infra/算法 团队管理系统 + + + 数据管理和配置面板 + + + + {!isAuthenticated ? ( +
+ + + 身份验证 + + 请输入访问码以继续 + + + + {SecurityManager.isLocked() && ( +
+ + + 访问已被锁定 {Math.ceil(SecurityManager.getRemainingLockTime() / 60000)} 分钟 + +
+ )} +
+ + setAccessCode(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleLogin()} + disabled={isValidating || SecurityManager.isLocked()} + /> +
+ +
+
+
+ ) : ( + + + 职位管理 + 数据导入 + 数据导出 + 历史版本 + + + +
+

职位列表 ({jobs.length})

+
+ + {jobs.length > 0 && ( + + )} +
+
+ +
+ {jobs.map((job) => ( + + +
+
+

{job.title}

+

+ {job.department} · { + job.priority === 'urgent' ? '🔥 紧急' : + job.priority === 'high' ? '⚡ 高优' : '📋 普通' + } +

+
+
+ + +
+
+
+
+ ))} +
+
+ + + + + 数据导入 + + 支持 JSON 和 Excel 格式文件导入 + + + +
+ + + +
+

支持格式:

+
    +
  • JSON 文件 (.json) - 标准数据格式
  • +
  • Excel 文件 (.xlsx, .xls) - 包含完整职位信息
  • +
+
+
+
+
+
+ + + + + 数据导出 + + 导出当前职位数据用于备份或分享 + + + + + + + + + + + + + 历史版本 + + 查看和恢复数据的历史版本 + + + +
+ {JobDataManager.getHistory().map((entry) => ( + + +
+
+

+ {new Date(entry.timestamp).toLocaleString('zh-CN')} +

+

+ {entry.count} 个职位 +

+
+ +
+
+
+ ))} +
+
+
+
+
+ )} + + {/* Job Edit Dialog */} + {editingJob && ( + { + setIsJobDialogOpen(false); + setEditingJob(null); + }} + onSave={handleSaveJob} + /> + )} + + {/* Delete All Jobs Confirmation Dialog */} + + + + + + 危险操作确认 + + + 此操作将永久删除所有 {jobs.length} 个职位数据。此操作不可逆! + + + +
+
+

+ ⚠️ 警告:此操作将: +

+
    +
  • • 删除所有 {jobs.length} 个职位
  • +
  • • 清空当前数据
  • +
  • • 在历史记录中保存删除前的数据
  • +
+
+ +
+ + setDeleteAllConfirmText(e.target.value)} + placeholder="输入 DELETE ALL" + className="mt-2" + /> +
+
+ +
+ + +
+
+
+
+
+ ); +} + +// Job editing dialog component +interface JobEditDialogProps { + job: JobPosition; + isOpen: boolean; + onClose: () => void; + onSave: (job: JobPosition) => void; +} + +function JobEditDialog({ job, isOpen, onClose, onSave }: JobEditDialogProps) { + const [editedJob, setEditedJob] = useState(job); + + const handleSave = () => { + if (!editedJob.title || !editedJob.department || !editedJob.url) { + return; + } + onSave(editedJob); + }; + + const updateField = (field: keyof JobPosition, value: any) => { + setEditedJob(prev => ({ ...prev, [field]: value })); + }; + + const updateArrayField = (field: 'description' | 'requirements' | 'bonus', value: string) => { + const array = value.split('\n').filter(Boolean); + setEditedJob(prev => ({ + ...prev, + [field]: field === 'bonus' ? (array.length > 0 ? array : undefined) : array + })); + }; + + return ( + + + + + {job.title ? '编辑职位' : '添加职位'} + + + +
+
+
+ + updateField('title', e.target.value)} + placeholder="例如:高性能网络专家" + /> +
+ +
+ + updateField('department', e.target.value)} + placeholder="例如:系统组直招" + /> +
+ +
+ + +
+ +
+ + updateField('url', e.target.value)} + placeholder="https://..." + /> +
+
+ +
+
+ +