diff --git a/.DS_Store b/.DS_Store index 694a98b..6e415a7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/dist.zip b/dist.zip index 0823387..0aa4c9f 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..dbd07c1 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/components/ContentViewer.jsx b/src/components/ContentViewer.jsx index 3a08b39..bf81e75 100644 --- a/src/components/ContentViewer.jsx +++ b/src/components/ContentViewer.jsx @@ -77,6 +77,7 @@ const ContentViewer = ({ ) : (
等待内容生成后查看脑图
diff --git a/src/components/Dropdown.css b/src/components/Dropdown.css new file mode 100644 index 0000000..55f9675 --- /dev/null +++ b/src/components/Dropdown.css @@ -0,0 +1,101 @@ +/* Dropdown Container */ +.dropdown-container { + position: relative; + display: inline-block; +} + +.dropdown-trigger-wrapper { + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +/* Dropdown Menu */ +.dropdown-menu-wrapper { + position: absolute; + top: calc(100% + 0.5rem); + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border: 1px solid #e2e8f0; + padding: 0.5rem; + min-width: 140px; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 2px; + animation: dropdown-fade-in 0.2s ease; +} + +@keyframes dropdown-fade-in { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Alignment */ +.dropdown-align-left { + left: 0; +} + +.dropdown-align-right { + right: 0; +} + +/* Menu Item */ +.dropdown-menu-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.65rem 1rem; + border: none; + background: none; + cursor: pointer; + border-radius: 6px; + font-size: 0.9375rem; + font-weight: 500; + color: #374151; + text-decoration: none; + transition: all 0.2s ease; + width: 100%; + text-align: left; + white-space: nowrap; +} + +.dropdown-menu-item:hover:not(:disabled) { + background: #f3f4f6; + color: #111827; +} + +.dropdown-menu-item:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Danger Item */ +.dropdown-menu-item.danger { + color: #dc2626; +} + +.dropdown-menu-item.danger:hover:not(:disabled) { + background: #fef2f2; + color: #b91c1c; +} + +/* Item Icon and Label */ +.dropdown-item-icon { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.dropdown-item-label { + flex: 1; +} diff --git a/src/components/Dropdown.jsx b/src/components/Dropdown.jsx new file mode 100644 index 0000000..cc8b62b --- /dev/null +++ b/src/components/Dropdown.jsx @@ -0,0 +1,81 @@ +import React, { useState, useEffect, useRef } from 'react'; +import './Dropdown.css'; + +/** + * Dropdown - 通用下拉菜单组件 + * + * @param {Object} props + * @param {React.ReactNode} props.trigger - 触发器元素(按钮) + * @param {Array} props.items - 菜单项数组 + * - label: string - 显示文本 + * - icon: React.ReactNode - 图标(可选) + * - onClick: function - 点击回调 + * - className: string - 自定义样式类(可选) + * - danger: boolean - 是否为危险操作(红色) + * @param {string} props.align - 对齐方式: 'left' | 'right',默认 'right' + * @param {string} props.className - 外层容器自定义类名 + */ +const Dropdown = ({ + trigger, + items = [], + align = 'right', + className = '' +}) => { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + // 点击外部关闭下拉菜单 + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen]); + + const handleTriggerClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsOpen(!isOpen); + }; + + const handleItemClick = (item, e) => { + e.preventDefault(); + e.stopPropagation(); + if (item.onClick) { + item.onClick(e); + } + setIsOpen(false); + }; + + return ( +
+
+ {trigger} +
+ + {isOpen && ( +
+ {items.map((item, index) => ( + + ))} +
+ )} +
+ ); +}; + +export default Dropdown; diff --git a/src/components/MarkdownEditor.css b/src/components/MarkdownEditor.css index b2a26d9..8b452e0 100644 --- a/src/components/MarkdownEditor.css +++ b/src/components/MarkdownEditor.css @@ -78,7 +78,7 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 100; min-width: 160px; - overflow: hidden; + overflow: visible; } .dropdown-menu button { diff --git a/src/components/MeetingTimeline.css b/src/components/MeetingTimeline.css index 38fd360..f80ba6a 100644 --- a/src/components/MeetingTimeline.css +++ b/src/components/MeetingTimeline.css @@ -99,68 +99,6 @@ margin-bottom: 0.75rem; } -.meeting-actions { - position: relative; - margin-left: 1rem; - flex-shrink: 0; -} - -.dropdown-trigger { - background: transparent; - border: none; - padding: 0.25rem; - border-radius: 4px; - cursor: pointer; - color: #64748b; - transition: all 0.3s ease; -} - -.dropdown-trigger:hover { - background: #f1f5f9; - color: #334155; -} - -.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; - min-width: 120px; - z-index: 10; - overflow: hidden; -} - -.dropdown-item { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - background: none; - border: none; - width: 100%; - text-align: left; - font-size: 0.9rem; - color: #334155; - text-decoration: none; - cursor: pointer; - transition: background 0.2s ease; -} - -.dropdown-item:hover { - background: #f8fafc; -} - -.dropdown-item.delete-item { - color: #ef4444; -} - -.dropdown-item.delete-item:hover { - background: #fef2f2; -} - /* Delete Modal */ .delete-modal-overlay { position: fixed; @@ -246,13 +184,6 @@ } /* Dropdown Menu Styles */ -.meeting-actions { - position: relative; - display: flex; - align-items: center; - gap: 0.5rem; -} - .dropdown-trigger { background: none; border: none; @@ -271,66 +202,6 @@ color: #334155; } -.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; -} - -/* 确保Link组件内的dropdown-item正确显示 */ -.dropdown-menu a { - text-decoration: none; - display: block; - width: 100%; -} - -.dropdown-menu a .dropdown-item { - width: 100%; -} - -.dropdown-item:hover { - background: #f3f4f6; - color: #111827; -} - -.dropdown-item.delete-item { - color: #dc2626; -} - -.dropdown-item.delete-item:hover { - background: #fef2f2; - color: #b91c1c; -} - .meeting-content { background: white; border-radius: 12px; diff --git a/src/components/MeetingTimeline.jsx b/src/components/MeetingTimeline.jsx index 92c685b..19865eb 100644 --- a/src/components/MeetingTimeline.jsx +++ b/src/components/MeetingTimeline.jsx @@ -7,23 +7,12 @@ import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; import TagDisplay from './TagDisplay'; import ConfirmDialog from './ConfirmDialog'; +import Dropdown from './Dropdown'; import './MeetingTimeline.css'; const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore = false, onLoadMore, loadingMore = false }) => { const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null); - const [showDropdown, setShowDropdown] = useState(null); const navigate = useNavigate(); - // Close dropdown when clicking outside - React.useEffect(() => { - const handleClickOutside = () => { - setShowDropdown(null); - }; - - if (showDropdown) { - document.addEventListener('click', handleClickOutside); - return () => document.removeEventListener('click', handleClickOutside); - } - }, [showDropdown]); const formatDateTime = (dateTimeString) => { if (!dateTimeString) return '时间待定'; @@ -67,19 +56,15 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore return lines.length > maxLines || summary.length > maxLength; }; - const handleEditClick = (meetingId, e) => { - e.preventDefault(); - navigate(`/meetings/edit/${meetingId}`) - setShowDropdown(null); + const handleEditClick = (meetingId) => { + navigate(`/meetings/edit/${meetingId}`); }; - const handleDeleteClick = (meeting, e) => { - e.preventDefault(); + const handleDeleteClick = (meeting) => { setDeleteConfirmInfo({ id: meeting.meeting_id, title: meeting.title }); - setShowDropdown(null); }; const handleConfirmDelete = async () => { @@ -89,12 +74,6 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore setDeleteConfirmInfo(null); }; - const toggleDropdown = (meetingId, e) => { - e.preventDefault(); - e.stopPropagation(); - setShowDropdown(showDropdown === meetingId ? null : meetingId); - }; - const sortedDates = Object.keys(meetingsByDate).sort((a, b) => new Date(b) - new Date(a)); if (sortedDates.length === 0) { @@ -142,31 +121,27 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore {isCreator && ( -
- - {showDropdown === meeting.meeting_id && ( -
e.stopPropagation()}> - - -
- )} -
+ + + + } + items={[ + { + icon: , + label: '编辑', + onClick: () => handleEditClick(meeting.meeting_id) + }, + { + icon: , + label: '删除', + onClick: () => handleDeleteClick(meeting), + danger: true + } + ]} + align="right" + /> )}
diff --git a/src/components/MindMap.jsx b/src/components/MindMap.jsx index 53a89ac..93d4c7a 100644 --- a/src/components/MindMap.jsx +++ b/src/components/MindMap.jsx @@ -14,8 +14,9 @@ import { Loader } from 'lucide-react'; * @param {Object} props * @param {string} props.content - Markdown格式的内容(必须由父组件准备好) * @param {string} props.title - 标题(用于显示) + * @param {number} props.initialScale - 初始缩放倍数,默认为1.8 */ -const MindMap = ({ content, title }) => { +const MindMap = ({ content, title, initialScale = 1.8 }) => { const [markdown, setMarkdown] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); @@ -144,11 +145,6 @@ const MindMap = ({ content, title }) => { try { const processedMarkdown = preprocessMarkdownForMindMap(markdown, title); - // console.log('=== 思维导图数据调试 ==='); - // console.log('原始markdown内容:'); - // console.log(markdown); - // console.log('预处理后的markdown:'); - // console.log(processedMarkdown); const transformer = new Transformer(); const { root } = transformer.transform(processedMarkdown); @@ -165,13 +161,9 @@ const MindMap = ({ content, title }) => { setTimeout(() => { if (markmapRef.current) { markmapRef.current.fit(); - // 在fit之后,再放大1.8倍以获得更好的可读性 + // 直接设置为指定的缩放倍数 try { - const { state } = markmapRef.current; - if (state) { - const currentScale = state.transform.k; - markmapRef.current.rescale(currentScale * 1.8); - } + markmapRef.current.rescale(initialScale); } catch (e) { console.log('缩放调整失败:', e); } @@ -183,7 +175,7 @@ const MindMap = ({ content, title }) => { setError('思维导图渲染失败'); } - }, [markdown, loading, title]); + }, [markdown, loading, title, initialScale]); if (loading) { return ( diff --git a/src/components/SimpleSearchInput.css b/src/components/SimpleSearchInput.css new file mode 100644 index 0000000..76747b0 --- /dev/null +++ b/src/components/SimpleSearchInput.css @@ -0,0 +1,64 @@ +/* 简洁搜索输入框样式 */ +.simple-search-input { + position: relative; + width: 100%; + display: flex; + align-items: center; +} + +.simple-search-input-field { + width: 100%; + padding: 0.5rem 0.75rem; + padding-right: 2.5rem; /* 为清除按钮留出空间 */ + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 0.875rem; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + outline: none; +} + +.simple-search-input-field:focus { + border-color: #667eea; + box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1); +} + +.simple-search-input-field::placeholder { + color: #94a3b8; +} + +.simple-search-clear-btn { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + padding: 0.25rem; + border: none; + background: transparent; + color: #94a3b8; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s ease; +} + +.simple-search-clear-btn:hover { + background: #f1f5f9; + color: #475569; +} + +.simple-search-clear-btn:active { + background: #e2e8f0; +} + +/* 禁用状态 */ +.simple-search-input-field:disabled { + background: #f8fafc; + color: #94a3b8; + cursor: not-allowed; +} + +.simple-search-input-field:disabled + .simple-search-clear-btn { + display: none; +} diff --git a/src/components/SimpleSearchInput.jsx b/src/components/SimpleSearchInput.jsx new file mode 100644 index 0000000..90f46f9 --- /dev/null +++ b/src/components/SimpleSearchInput.jsx @@ -0,0 +1,90 @@ +import React, { useRef, useEffect, useState } from 'react'; +import { X } from 'lucide-react'; +import './SimpleSearchInput.css'; + +const SimpleSearchInput = ({ + value = '', + onChange, + placeholder = '搜索...', + className = '', + debounceDelay = 500, + realTimeSearch = true +}) => { + const debounceTimerRef = useRef(null); + const [localValue, setLocalValue] = useState(value); + + // 同步外部value的变化到本地状态 + useEffect(() => { + setLocalValue(value); + }, [value]); + + const handleInputChange = (e) => { + const inputValue = e.target.value; + + // 立即更新本地状态,保证输入流畅 + setLocalValue(inputValue); + + // 通知父组件 + if (onChange) { + if (realTimeSearch) { + // 使用防抖 + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + + debounceTimerRef.current = setTimeout(() => { + onChange(inputValue); + }, debounceDelay); + } else { + // 不使用防抖,立即触发 + onChange(inputValue); + } + } + }; + + const handleClear = () => { + // 清除防抖定时器 + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + + // 立即更新本地状态和通知父组件 + setLocalValue(''); + if (onChange) { + onChange(''); + } + }; + + // 组件卸载时清除定时器 + useEffect(() => { + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + }; + }, []); + + return ( +
+ + {localValue && ( + + )} +
+ ); +}; + +export default SimpleSearchInput; diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css index bcb022b..c7c41d5 100644 --- a/src/pages/Dashboard.css +++ b/src/pages/Dashboard.css @@ -449,7 +449,6 @@ background: white; border-radius: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - overflow: hidden; } .section-header { @@ -583,43 +582,6 @@ background: #f1f5f9; } -.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; - width: 160px; - z-index: 10; - display: flex; - flex-direction: column; -} - -.dropdown-menu button, -.dropdown-menu a { - background: none; - border: none; - text-align: left; - padding: 0.75rem 1rem; - width: 100%; - border-radius: 6px; - cursor: pointer; - color: #334155; - text-decoration: none; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.dropdown-menu button:hover, -.dropdown-menu a:hover { - background: #f1f5f9; -} - /* Modal Styles */ .modal-overlay { position: fixed; diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 21aa748..0a726ed 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -5,11 +5,12 @@ import { Link } from 'react-router-dom'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import MeetingTimeline from '../components/MeetingTimeline'; import TagCloud from '../components/TagCloud'; -import ExpandSearchBox from '../components/ExpandSearchBox'; +import SimpleSearchInput from '../components/SimpleSearchInput'; import VoiceprintCollectionModal from '../components/VoiceprintCollectionModal'; import ConfirmDialog from '../components/ConfirmDialog'; import PageLoading from '../components/PageLoading'; import ScrollToTop from '../components/ScrollToTop'; +import Dropdown from '../components/Dropdown'; import meetingCacheService from '../services/meetingCacheService'; import './Dashboard.css'; @@ -24,14 +25,12 @@ const Dashboard = ({ user, onLogout }) => { const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(''); - const [dropdownOpen, setDropdownOpen] = useState(false); const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); const [oldPassword, setOldPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [passwordChangeError, setPasswordChangeError] = useState(''); const [passwordChangeSuccess, setPasswordChangeSuccess] = useState(''); - const dropdownRef = useRef(null); // 声纹相关状态 const [voiceprintStatus, setVoiceprintStatus] = useState(null); @@ -206,18 +205,6 @@ const Dashboard = ({ user, onLogout }) => { setSearchQuery(''); }; - useEffect(() => { - const handleClickOutside = (event) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { - setDropdownOpen(false); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [dropdownRef]); - const fetchUserData = async () => { try { console.log('Fetching user data for user_id:', user.user_id); @@ -359,22 +346,38 @@ const Dashboard = ({ user, onLogout }) => { iMeeting
-
-
setDropdownOpen(!dropdownOpen)}> - 欢迎,{userInfo?.caption} - -
- {dropdownOpen && ( -
- - setDropdownOpen(false)}> 提示词仓库 - {user.role_id === 1 && ( - setDropdownOpen(false)}> 平台管理 - )} - + + 欢迎,{userInfo?.caption} +
- )} -
+ } + items={[ + { + icon: , + label: '修改密码', + onClick: () => setShowChangePasswordModal(true) + }, + { + icon: , + label: '提示词仓库', + onClick: () => window.location.href = '/prompt-management' + }, + ...(user.role_id === 1 ? [{ + icon: , + label: '平台管理', + onClick: () => window.location.href = '/admin/management' + }] : []), + { + icon: , + label: '退出登录', + onClick: onLogout + } + ]} + align="right" + className="user-menu-dropdown" + />
@@ -484,13 +487,12 @@ const Dashboard = ({ user, onLogout }) => { {/* 搜索和标签过滤卡片 */}
-
diff --git a/src/pages/KnowledgeBasePage.jsx b/src/pages/KnowledgeBasePage.jsx index f542987..874eff0 100644 --- a/src/pages/KnowledgeBasePage.jsx +++ b/src/pages/KnowledgeBasePage.jsx @@ -7,6 +7,7 @@ import ContentViewer from '../components/ContentViewer'; import TagDisplay from '../components/TagDisplay'; import Toast from '../components/Toast'; import ConfirmDialog from '../components/ConfirmDialog'; +import SimpleSearchInput from '../components/SimpleSearchInput'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; @@ -836,12 +837,12 @@ const KnowledgeBasePage = ({ user }) => {
{/* 紧凑的搜索和过滤区 */}
- setSearchQuery(e.target.value)} - className="compact-search-input" + onChange={setSearchQuery} + placeholder="搜索会议名称或创建人..." + realTimeSearch={true} + debounceDelay={500} /> {availableTags.length > 0 && ( diff --git a/src/pages/MeetingDetails.css b/src/pages/MeetingDetails.css index 9ffa517..cef64a0 100644 --- a/src/pages/MeetingDetails.css +++ b/src/pages/MeetingDetails.css @@ -1526,8 +1526,8 @@ font-size: 0.8rem; min-width: 80px; } - - .action-btn span { + + .action-btn.icon-only span { display: none; } diff --git a/src/pages/MeetingPreview.css b/src/pages/MeetingPreview.css index 96fbb28..6abf560 100644 --- a/src/pages/MeetingPreview.css +++ b/src/pages/MeetingPreview.css @@ -150,6 +150,25 @@ margin-bottom: 40px; } +/* Tab 样式 */ +.preview-tabs { + margin-top: 20px; +} + +.preview-tabs .ant-tabs-nav { + margin-bottom: 20px; +} + +.preview-tabs .ant-tabs-tab { + font-size: 16px; + font-weight: 500; + padding: 12px 24px; +} + +.preview-tabs .ant-tabs-tab-active { + font-weight: 600; +} + .summary-content { font-size: 15px; line-height: 1.8; @@ -157,6 +176,48 @@ overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; + padding: 20px 0; +} + +/* 思维导图容器样式 */ +.mindmap-wrapper { + width: 100%; + min-height: 500px; + height: 600px; + background: white; + border: 1px solid #e5e7eb; + border-radius: 8px; + overflow: hidden; + position: relative; +} + +.mindmap-wrapper .mindmap-container { + width: 100%; + height: 100%; +} + +.mindmap-wrapper .markmap-render-area { + width: 100%; + height: 100%; + background: white; +} + +.mindmap-wrapper .mindmap-loading, +.mindmap-wrapper .mindmap-error { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: #64748b; +} + +.mindmap-wrapper .mindmap-loading svg { + animation: spin 1s linear infinite; +} + +.mindmap-wrapper .mindmap-error { + color: #dc2626; } /* Markdown 样式 */ @@ -333,8 +394,40 @@ font-size: 14px; } + /* Tab 移动端优化 */ + .preview-tabs .ant-tabs-nav { + margin-bottom: 15px; + } + + .preview-tabs .ant-tabs-nav-list { + width: 100%; + display: flex; + } + + .preview-tabs .ant-tabs-tab { + font-size: 15px; + padding: 12px 20px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + margin: 0 !important; + } + + .preview-tabs .ant-tabs-tab + .ant-tabs-tab { + margin: 0 !important; + } + + /* 思维导图移动端优化 */ + .mindmap-wrapper { + min-height: 400px; + height: 500px; + border-radius: 6px; + } + .summary-content { font-size: 14px; + padding: 15px 0; } .summary-content h1 { @@ -385,6 +478,11 @@ .preview-title { font-size: 24px; } + + /* 思维导图平板优化 */ + .mindmap-wrapper { + height: 550px; + } } /* 打印样式优化 */ diff --git a/src/pages/MeetingPreview.jsx b/src/pages/MeetingPreview.jsx index cdabe06..7d2349e 100644 --- a/src/pages/MeetingPreview.jsx +++ b/src/pages/MeetingPreview.jsx @@ -4,8 +4,12 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; +import { Tabs } from 'antd'; +import MindMap from '../components/MindMap'; import './MeetingPreview.css'; +const { TabPane } = Tabs; + const MeetingPreview = () => { const { meeting_id } = useParams(); const [meetingData, setMeetingData] = useState(null); @@ -20,8 +24,8 @@ const MeetingPreview = () => { const fetchMeetingPreviewData = async () => { try { setLoading(true); - const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000'; - const response = await fetch(`${apiUrl}/api/meetings/${meeting_id}/preview-data`); + // Use relative path to work in both dev and production + const response = await fetch(`/api/meetings/${meeting_id}/preview-data`); const result = await response.json(); if (result.code === "200") { @@ -132,15 +136,28 @@ const MeetingPreview = () => {
-

📝 会议摘要

-
- - {meetingData.summary} - -
+

📝 会议内容

+ + +
+ + {meetingData.summary} + +
+
+ +
+ +
+
+