|
@@ -0,0 +1,309 @@
|
|
|
|
|
+import React, { useState } from 'react';
|
|
|
|
|
+import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
|
+import { useQuery } from '@tanstack/react-query';
|
|
|
|
|
+import { useSilverWisdomDetail } from '@/client/mobile/hooks/useSilverWisdomData';
|
|
|
|
|
+import { silverKnowledgeClient } from '@/client/api';
|
|
|
|
|
+import { ChevronLeftIcon, HeartIcon, BookmarkIcon, ShareIcon, ArrowDownTrayIcon, EyeIcon } from '@heroicons/react/24/outline';
|
|
|
|
|
+import { HeartIcon as HeartSolidIcon, BookmarkIcon as BookmarkSolidIcon } from '@heroicons/react/24/solid';
|
|
|
|
|
+import dayjs from 'dayjs';
|
|
|
|
|
+import 'dayjs/locale/zh-cn';
|
|
|
|
|
+
|
|
|
|
|
+dayjs.locale('zh-cn');
|
|
|
|
|
+
|
|
|
|
|
+// 色彩系统
|
|
|
|
|
+const COLORS = {
|
|
|
|
|
+ ink: {
|
|
|
|
|
+ light: '#f5f3f0', // 宣纸背景色
|
|
|
|
|
+ medium: '#d4c4a8', // 淡墨
|
|
|
|
|
+ dark: '#8b7355', // 浓墨
|
|
|
|
|
+ deep: '#3a2f26', // 焦墨
|
|
|
|
|
+ },
|
|
|
|
|
+ accent: {
|
|
|
|
|
+ red: '#a85c5c', // 朱砂
|
|
|
|
|
+ blue: '#4a6b7c', // 花青
|
|
|
|
|
+ green: '#5c7c5c', // 石绿
|
|
|
|
|
+ },
|
|
|
|
|
+ text: {
|
|
|
|
|
+ primary: '#2f1f0f', // 墨色文字
|
|
|
|
|
+ secondary: '#5d4e3b', // 淡墨文字
|
|
|
|
|
+ light: '#8b7355', // 极淡文字
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 字体样式
|
|
|
|
|
+const FONT_STYLES = {
|
|
|
|
|
+ title: 'font-serif text-2xl font-bold tracking-wide',
|
|
|
|
|
+ sectionTitle: 'font-serif text-xl font-semibold tracking-wide',
|
|
|
|
|
+ body: 'font-sans text-base leading-relaxed',
|
|
|
|
|
+ caption: 'font-sans text-sm',
|
|
|
|
|
+ small: 'font-sans text-xs',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const SilverWisdomDetailPage: React.FC = () => {
|
|
|
|
|
+ const { id } = useParams<{ id: string }>();
|
|
|
|
|
+ const navigate = useNavigate();
|
|
|
|
|
+ const [isLiked, setIsLiked] = useState(false);
|
|
|
|
|
+ const [isBookmarked, setIsBookmarked] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ const { data, isLoading, error } = useSilverWisdomDetail(Number(id));
|
|
|
|
|
+
|
|
|
|
|
+ if (isLoading) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
|
|
+ <div className="text-center">
|
|
|
|
|
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2" style={{ borderColor: COLORS.ink.dark }}></div>
|
|
|
|
|
+ <p className="mt-2" style={{ color: COLORS.text.secondary }}>加载中...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (error || !data) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
|
|
+ <div className="text-center">
|
|
|
|
|
+ <p style={{ color: COLORS.text.primary }}>获取知识详情失败</p>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => navigate('/silver-wisdom')}
|
|
|
|
|
+ className="mt-4 px-4 py-2 rounded-full text-white transition-colors"
|
|
|
|
|
+ style={{ backgroundColor: COLORS.ink.dark }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 返回列表
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const knowledge = data.data;
|
|
|
|
|
+
|
|
|
|
|
+ const handleShare = () => {
|
|
|
|
|
+ if (navigator.share) {
|
|
|
|
|
+ navigator.share({
|
|
|
|
|
+ title: knowledge.title,
|
|
|
|
|
+ text: knowledge.content.substring(0, 100) + '...',
|
|
|
|
|
+ url: window.location.href,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDownload = () => {
|
|
|
|
|
+ if (knowledge.attachment) {
|
|
|
|
|
+ window.open(knowledge.attachment, '_blank');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const formatDate = (date: string) => {
|
|
|
|
|
+ return dayjs(date).format('YYYY年MM月DD日 HH:mm');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const getReadTime = (content: string) => {
|
|
|
|
|
+ const wordsPerMinute = 200;
|
|
|
|
|
+ const wordCount = content.length;
|
|
|
|
|
+ return Math.ceil(wordCount / wordsPerMinute);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
|
|
+ {/* 头部导航 */}
|
|
|
|
|
+ <header
|
|
|
|
|
+ className="sticky top-0 z-10 px-4 py-3 flex items-center border-b"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ backgroundColor: COLORS.ink.light,
|
|
|
|
|
+ borderColor: COLORS.ink.medium
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => navigate('/silver-wisdom')}
|
|
|
|
|
+ className="p-2 rounded-full hover:transition-colors"
|
|
|
|
|
+ style={{ color: COLORS.ink.dark }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <ChevronLeftIcon className="w-6 h-6" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <h1 className="flex-1 text-center text-lg font-medium" style={{ color: COLORS.text.primary }}>
|
|
|
|
|
+ 知识详情
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={handleShare}
|
|
|
|
|
+ className="p-2 rounded-full hover:transition-colors"
|
|
|
|
|
+ style={{ color: COLORS.ink.dark }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <ShareIcon className="w-5 h-5" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </header>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="pb-20">
|
|
|
|
|
+ {/* 封面图片 */}
|
|
|
|
|
+ {knowledge.coverImage && (
|
|
|
|
|
+ <div className="relative h-48 overflow-hidden">
|
|
|
|
|
+ <img
|
|
|
|
|
+ src={knowledge.coverImage}
|
|
|
|
|
+ alt={knowledge.title}
|
|
|
|
|
+ className="w-full h-full object-cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 内容区域 */}
|
|
|
|
|
+ <div className="px-4 py-6">
|
|
|
|
|
+ {/* 标题区域 */}
|
|
|
|
|
+ <div className="mb-6">
|
|
|
|
|
+ <h1 className={FONT_STYLES.title} style={{ color: COLORS.text.primary }}>
|
|
|
|
|
+ {knowledge.title}
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ <div className="flex items-center mt-3 space-x-4">
|
|
|
|
|
+ <span className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
|
|
+ 作者: {knowledge.author}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
|
|
+ {formatDate(knowledge.createdAt)}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
|
|
+ {getReadTime(knowledge.content)}分钟阅读
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center mt-2 space-x-2">
|
|
|
|
|
+ <span className="px-2 py-1 rounded-full text-xs" style={{
|
|
|
|
|
+ backgroundColor: COLORS.accent.blue,
|
|
|
|
|
+ color: 'white'
|
|
|
|
|
+ }}>
|
|
|
|
|
+ {knowledge.category?.name || '未分类'}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ {knowledge.tags && (
|
|
|
|
|
+ <span className="px-2 py-1 rounded-full text-xs" style={{
|
|
|
|
|
+ backgroundColor: COLORS.ink.medium,
|
|
|
|
|
+ color: COLORS.text.primary
|
|
|
|
|
+ }}>
|
|
|
|
|
+ {knowledge.tags}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 内容正文 */}
|
|
|
|
|
+ <div className="mb-8">
|
|
|
|
|
+ <div className={`${FONT_STYLES.body} whitespace-pre-wrap`} style={{ color: COLORS.text.primary }}>
|
|
|
|
|
+ {knowledge.content}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 附件区域 */}
|
|
|
|
|
+ {knowledge.attachment && (
|
|
|
|
|
+ <div className="mb-8">
|
|
|
|
|
+ <h3 className={FONT_STYLES.sectionTitle} style={{ color: COLORS.text.primary }}>
|
|
|
|
|
+ 附件资料
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <div className="mt-4 rounded-xl border p-4"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)'
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
|
|
+ <div className="flex items-center">
|
|
|
|
|
+ <ArrowDownTrayIcon className="w-5 h-5 mr-2" style={{ color: COLORS.ink.dark }} />
|
|
|
|
|
+ <span style={{ color: COLORS.text.primary }}>{knowledge.attachmentName || '附件'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex space-x-2">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={handleDownload}
|
|
|
|
|
+ className="px-3 py-1 rounded-full text-sm transition-colors"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ backgroundColor: COLORS.ink.dark,
|
|
|
|
|
+ color: 'white'
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 下载
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => window.open(knowledge.attachment, '_blank')}
|
|
|
|
|
+ className="px-3 py-1 rounded-full text-sm border transition-colors"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
|
|
+ color: COLORS.ink.dark
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 预览
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* PDF预览 */}
|
|
|
|
|
+ <div className="mt-4">
|
|
|
|
|
+ <iframe
|
|
|
|
|
+ src={knowledge.attachment}
|
|
|
|
|
+ className="w-full h-64 rounded-lg border"
|
|
|
|
|
+ style={{ borderColor: COLORS.ink.medium }}
|
|
|
|
|
+ title={knowledge.attachmentName || 'PDF预览'}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 互动统计 */}
|
|
|
|
|
+ <div className="flex items-center justify-between mb-6">
|
|
|
|
|
+ <div className="flex items-center space-x-4">
|
|
|
|
|
+ <div className="flex items-center">
|
|
|
|
|
+ <EyeIcon className="w-5 h-5 mr-1" style={{ color: COLORS.text.secondary }} />
|
|
|
|
|
+ <span style={{ color: COLORS.text.secondary }}>{knowledge.viewCount}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center">
|
|
|
|
|
+ <HeartIcon className="w-5 h-5 mr-1" style={{ color: COLORS.text.secondary }} />
|
|
|
|
|
+ <span style={{ color: COLORS.text.secondary }}>{knowledge.likeCount}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 底部互动栏 */}
|
|
|
|
|
+ <div className="fixed bottom-0 left-0 right-0 border-t p-4"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ backgroundColor: COLORS.ink.light,
|
|
|
|
|
+ borderColor: COLORS.ink.medium
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="flex items-center justify-around max-w-md mx-auto">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => setIsLiked(!isLiked)}
|
|
|
|
|
+ className="flex flex-col items-center p-2 rounded-lg transition-colors"
|
|
|
|
|
+ style={{ color: isLiked ? COLORS.accent.red : COLORS.text.secondary }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {isLiked ? (
|
|
|
|
|
+ <HeartSolidIcon className="w-6 h-6" />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <HeartIcon className="w-6 h-6" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ <span className="text-xs mt-1">点赞</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => setIsBookmarked(!isBookmarked)}
|
|
|
|
|
+ className="flex flex-col items-center p-2 rounded-lg transition-colors"
|
|
|
|
|
+ style={{ color: isBookmarked ? COLORS.accent.blue : COLORS.text.secondary }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {isBookmarked ? (
|
|
|
|
|
+ <BookmarkSolidIcon className="w-6 h-6" />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <BookmarkIcon className="w-6 h-6" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ <span className="text-xs mt-1">收藏</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={handleShare}
|
|
|
|
|
+ className="flex flex-col items-center p-2 rounded-lg transition-colors"
|
|
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <ShareIcon className="w-6 h-6" />
|
|
|
|
|
+ <span className="text-xs mt-1">分享</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default SilverWisdomDetailPage;
|