import { useEffect, useRef, useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, Check, Loader2, ImagePlus, IdCard } from 'lucide-react'; import CodeMirror, { type ReactCodeMirrorRef } from '@uiw/react-codemirror'; import { markdown } from '@codemirror/lang-markdown'; import { oneDark } from '@codemirror/theme-one-dark'; import { motion } from 'framer-motion'; import { api } from '../../api'; import { Markdown } from '../../components/Markdown'; import { useThemeStore } from '../../store/theme'; import { defaultProfile, parseProfile, type ProfileContent } from '../../config/profile'; /** 后台"个人主页"编辑:名片信息(头像/联系方式)+ Markdown 正文 */ export function AdminProfilePage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { theme } = useThemeStore(); const [content, setContent] = useState(''); const [card, setCard] = useState(defaultProfile); const [initialized, setInitialized] = useState(false); const [savedAt, setSavedAt] = useState(null); const [showCard, setShowCard] = useState(false); const editorRef = useRef(null); const fileInputRef = useRef(null); const avatarInputRef = useRef(null); const { data: page, isLoading } = useQuery({ queryKey: ['page', 'profile'], queryFn: () => api.getPage('profile'), }); const { data: cardPage } = useQuery({ queryKey: ['page', 'profile-card'], queryFn: () => api.getPage('profile-card'), }); useEffect(() => { if (!initialized && page && cardPage) { setContent(page.content_md || '# 关于我\n\n'); setCard(parseProfile(cardPage.content_md)); setInitialized(true); } }, [page, cardPage, initialized]); const saveMutation = useMutation({ mutationFn: () => api.savePage('profile', content), onSuccess: () => { setSavedAt(new Date()); queryClient.invalidateQueries({ queryKey: ['page', 'profile'] }); }, }); const saveCardMutation = useMutation({ mutationFn: () => api.savePage('profile-card', JSON.stringify(card)), onSuccess: () => { setSavedAt(new Date()); queryClient.invalidateQueries({ queryKey: ['page', 'profile-card'] }); }, }); const uploadMutation = useMutation({ mutationFn: (file: File) => api.uploadImage(file), onSuccess: ({ url }, file) => { // 在光标处插入图片 Markdown,没有光标就追加到末尾 const view = editorRef.current?.view; const snippet = `![${file.name}](${url})`; if (view) { const pos = view.state.selection.main.head; view.dispatch({ changes: { from: pos, insert: snippet } }); } else { setContent((c) => `${c}\n${snippet}\n`); } }, onError: (e) => alert(`上传失败:${e.message}`), }); const avatarUploadMutation = useMutation({ mutationFn: (file: File) => api.uploadImage(file), onSuccess: ({ url }) => setCard((c) => ({ ...c, avatar_url: url })), onError: (e) => alert(`上传失败:${e.message}`), }); if (isLoading || !initialized) { return (
); } const inputCls = 'w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm outline-none focus:border-indigo-500 dark:border-indigo-500/20 dark:bg-space-800 dark:text-star'; const labelCls = 'mb-1 block text-xs font-medium text-slate-500 dark:text-star-dim'; const setCardField = (key: keyof ProfileContent) => (e: React.ChangeEvent) => setCard({ ...card, [key]: e.target.value }); return (
个人主页
{ const file = e.target.files?.[0]; if (file) uploadMutation.mutate(file); e.target.value = ''; }} />
{/* 名片信息面板 */} {showCard && (
{card.avatar_url ? ( avatar ) : (
{card.name.charAt(0).toUpperCase()}
)} { const file = e.target.files?.[0]; if (file) avatarUploadMutation.mutate(file); e.target.value = ''; }} />
)}
setContent(value)} className="flex-1 min-h-0 text-sm" basicSetup={{ lineNumbers: true, highlightActiveLineGutter: true, highlightActiveLine: true, foldGutter: false }} />
); }