修复了前端的问题
parent
06f1b959b4
commit
e5b04ed2d6
Binary file not shown.
|
|
@ -77,6 +77,7 @@ const ContentViewer = ({
|
||||||
<MindMap
|
<MindMap
|
||||||
content={content}
|
content={content}
|
||||||
title={title}
|
title={title}
|
||||||
|
initialScale={1.8}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="empty-content">等待内容生成后查看脑图</div>
|
<div className="empty-content">等待内容生成后查看脑图</div>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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<Object>} 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 (
|
||||||
|
<div className={`dropdown-container ${className}`} ref={dropdownRef}>
|
||||||
|
<div className="dropdown-trigger-wrapper" onClick={handleTriggerClick}>
|
||||||
|
{trigger}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className={`dropdown-menu-wrapper dropdown-align-${align}`}>
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`dropdown-menu-item ${item.danger ? 'danger' : ''} ${item.className || ''}`}
|
||||||
|
onClick={(e) => handleItemClick(item, e)}
|
||||||
|
disabled={item.disabled}
|
||||||
|
>
|
||||||
|
{item.icon && <span className="dropdown-item-icon">{item.icon}</span>}
|
||||||
|
<span className="dropdown-item-label">{item.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu button {
|
.dropdown-menu button {
|
||||||
|
|
|
||||||
|
|
@ -99,68 +99,6 @@
|
||||||
margin-bottom: 0.75rem;
|
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 */
|
||||||
.delete-modal-overlay {
|
.delete-modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
@ -246,13 +184,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dropdown Menu Styles */
|
/* Dropdown Menu Styles */
|
||||||
.meeting-actions {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-trigger {
|
.dropdown-trigger {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -271,66 +202,6 @@
|
||||||
color: #334155;
|
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 {
|
.meeting-content {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,12 @@ import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
import TagDisplay from './TagDisplay';
|
import TagDisplay from './TagDisplay';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
|
import Dropdown from './Dropdown';
|
||||||
import './MeetingTimeline.css';
|
import './MeetingTimeline.css';
|
||||||
|
|
||||||
const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore = false, onLoadMore, loadingMore = false }) => {
|
const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore = false, onLoadMore, loadingMore = false }) => {
|
||||||
const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null);
|
const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null);
|
||||||
const [showDropdown, setShowDropdown] = useState(null);
|
|
||||||
const navigate = useNavigate();
|
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) => {
|
const formatDateTime = (dateTimeString) => {
|
||||||
if (!dateTimeString) return '时间待定';
|
if (!dateTimeString) return '时间待定';
|
||||||
|
|
@ -67,19 +56,15 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore
|
||||||
return lines.length > maxLines || summary.length > maxLength;
|
return lines.length > maxLines || summary.length > maxLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditClick = (meetingId, e) => {
|
const handleEditClick = (meetingId) => {
|
||||||
e.preventDefault();
|
navigate(`/meetings/edit/${meetingId}`);
|
||||||
navigate(`/meetings/edit/${meetingId}`)
|
|
||||||
setShowDropdown(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteClick = (meeting, e) => {
|
const handleDeleteClick = (meeting) => {
|
||||||
e.preventDefault();
|
|
||||||
setDeleteConfirmInfo({
|
setDeleteConfirmInfo({
|
||||||
id: meeting.meeting_id,
|
id: meeting.meeting_id,
|
||||||
title: meeting.title
|
title: meeting.title
|
||||||
});
|
});
|
||||||
setShowDropdown(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmDelete = async () => {
|
const handleConfirmDelete = async () => {
|
||||||
|
|
@ -89,12 +74,6 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore
|
||||||
setDeleteConfirmInfo(null);
|
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));
|
const sortedDates = Object.keys(meetingsByDate).sort((a, b) => new Date(b) - new Date(a));
|
||||||
|
|
||||||
if (sortedDates.length === 0) {
|
if (sortedDates.length === 0) {
|
||||||
|
|
@ -142,31 +121,27 @@ const MeetingTimeline = ({ meetingsByDate, currentUser, onDeleteMeeting, hasMore
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{isCreator && (
|
{isCreator && (
|
||||||
<div className="meeting-actions">
|
<Dropdown
|
||||||
<button
|
trigger={
|
||||||
className="dropdown-trigger"
|
<button className="dropdown-trigger">
|
||||||
onClick={(e) => toggleDropdown(meeting.meeting_id, e)}
|
<MoreVertical size={18} />
|
||||||
>
|
</button>
|
||||||
<MoreVertical size={18} />
|
}
|
||||||
</button>
|
items={[
|
||||||
{showDropdown === meeting.meeting_id && (
|
{
|
||||||
<div className="dropdown-menu" onClick={(e) => e.stopPropagation()}>
|
icon: <Edit size={16} />,
|
||||||
<button className="dropdown-item"
|
label: '编辑',
|
||||||
onClick={(e) =>handleEditClick(meeting.meeting_id, e)}
|
onClick: () => handleEditClick(meeting.meeting_id)
|
||||||
>
|
},
|
||||||
<Edit size={16} />
|
{
|
||||||
编辑
|
icon: <Trash2 size={16} />,
|
||||||
</button>
|
label: '删除',
|
||||||
<button
|
onClick: () => handleDeleteClick(meeting),
|
||||||
className="dropdown-item delete-item"
|
danger: true
|
||||||
onClick={(e) => handleDeleteClick(meeting, e)}
|
}
|
||||||
>
|
]}
|
||||||
<Trash2 size={16} />
|
align="right"
|
||||||
删除
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="meeting-meta">
|
<div className="meeting-meta">
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@ import { Loader } from 'lucide-react';
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {string} props.content - Markdown格式的内容(必须由父组件准备好)
|
* @param {string} props.content - Markdown格式的内容(必须由父组件准备好)
|
||||||
* @param {string} props.title - 标题(用于显示)
|
* @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 [markdown, setMarkdown] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
@ -144,11 +145,6 @@ const MindMap = ({ content, title }) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const processedMarkdown = preprocessMarkdownForMindMap(markdown, title);
|
const processedMarkdown = preprocessMarkdownForMindMap(markdown, title);
|
||||||
// console.log('=== 思维导图数据调试 ===');
|
|
||||||
// console.log('原始markdown内容:');
|
|
||||||
// console.log(markdown);
|
|
||||||
// console.log('预处理后的markdown:');
|
|
||||||
// console.log(processedMarkdown);
|
|
||||||
|
|
||||||
const transformer = new Transformer();
|
const transformer = new Transformer();
|
||||||
const { root } = transformer.transform(processedMarkdown);
|
const { root } = transformer.transform(processedMarkdown);
|
||||||
|
|
@ -165,13 +161,9 @@ const MindMap = ({ content, title }) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (markmapRef.current) {
|
if (markmapRef.current) {
|
||||||
markmapRef.current.fit();
|
markmapRef.current.fit();
|
||||||
// 在fit之后,再放大1.8倍以获得更好的可读性
|
// 直接设置为指定的缩放倍数
|
||||||
try {
|
try {
|
||||||
const { state } = markmapRef.current;
|
markmapRef.current.rescale(initialScale);
|
||||||
if (state) {
|
|
||||||
const currentScale = state.transform.k;
|
|
||||||
markmapRef.current.rescale(currentScale * 1.8);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('缩放调整失败:', e);
|
console.log('缩放调整失败:', e);
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +175,7 @@ const MindMap = ({ content, title }) => {
|
||||||
setError('思维导图渲染失败');
|
setError('思维导图渲染失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [markdown, loading, title]);
|
}, [markdown, loading, title, initialScale]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
<div className={`simple-search-input ${className}`}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={localValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="simple-search-input-field"
|
||||||
|
/>
|
||||||
|
{localValue && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="simple-search-clear-btn"
|
||||||
|
onClick={handleClear}
|
||||||
|
aria-label="清除搜索"
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SimpleSearchInput;
|
||||||
|
|
@ -449,7 +449,6 @@
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
|
|
@ -583,43 +582,6 @@
|
||||||
background: #f1f5f9;
|
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 Styles */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ import { Link } from 'react-router-dom';
|
||||||
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
|
||||||
import MeetingTimeline from '../components/MeetingTimeline';
|
import MeetingTimeline from '../components/MeetingTimeline';
|
||||||
import TagCloud from '../components/TagCloud';
|
import TagCloud from '../components/TagCloud';
|
||||||
import ExpandSearchBox from '../components/ExpandSearchBox';
|
import SimpleSearchInput from '../components/SimpleSearchInput';
|
||||||
import VoiceprintCollectionModal from '../components/VoiceprintCollectionModal';
|
import VoiceprintCollectionModal from '../components/VoiceprintCollectionModal';
|
||||||
import ConfirmDialog from '../components/ConfirmDialog';
|
import ConfirmDialog from '../components/ConfirmDialog';
|
||||||
import PageLoading from '../components/PageLoading';
|
import PageLoading from '../components/PageLoading';
|
||||||
import ScrollToTop from '../components/ScrollToTop';
|
import ScrollToTop from '../components/ScrollToTop';
|
||||||
|
import Dropdown from '../components/Dropdown';
|
||||||
import meetingCacheService from '../services/meetingCacheService';
|
import meetingCacheService from '../services/meetingCacheService';
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
|
|
||||||
|
|
@ -24,14 +25,12 @@ const Dashboard = ({ user, onLogout }) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
|
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
|
||||||
const [oldPassword, setOldPassword] = useState('');
|
const [oldPassword, setOldPassword] = useState('');
|
||||||
const [newPassword, setNewPassword] = useState('');
|
const [newPassword, setNewPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
const [passwordChangeError, setPasswordChangeError] = useState('');
|
const [passwordChangeError, setPasswordChangeError] = useState('');
|
||||||
const [passwordChangeSuccess, setPasswordChangeSuccess] = useState('');
|
const [passwordChangeSuccess, setPasswordChangeSuccess] = useState('');
|
||||||
const dropdownRef = useRef(null);
|
|
||||||
|
|
||||||
// 声纹相关状态
|
// 声纹相关状态
|
||||||
const [voiceprintStatus, setVoiceprintStatus] = useState(null);
|
const [voiceprintStatus, setVoiceprintStatus] = useState(null);
|
||||||
|
|
@ -206,18 +205,6 @@ const Dashboard = ({ user, onLogout }) => {
|
||||||
setSearchQuery('');
|
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 () => {
|
const fetchUserData = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('Fetching user data for user_id:', user.user_id);
|
console.log('Fetching user data for user_id:', user.user_id);
|
||||||
|
|
@ -359,22 +346,38 @@ const Dashboard = ({ user, onLogout }) => {
|
||||||
<span className="logo-text">iMeeting</span>
|
<span className="logo-text">iMeeting</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="user-actions">
|
<div className="user-actions">
|
||||||
<div className="user-menu-container" ref={dropdownRef}>
|
<Dropdown
|
||||||
<div className="user-menu-trigger" onClick={() => setDropdownOpen(!dropdownOpen)}>
|
trigger={
|
||||||
<span className="welcome-text">欢迎,{userInfo?.caption}</span>
|
<div className="user-menu-trigger">
|
||||||
<ChevronDown size={20} />
|
<span className="welcome-text">欢迎,{userInfo?.caption}</span>
|
||||||
</div>
|
<ChevronDown size={20} />
|
||||||
{dropdownOpen && (
|
|
||||||
<div className="dropdown-menu">
|
|
||||||
<button onClick={() => { setShowChangePasswordModal(true); setDropdownOpen(false); }}><KeyRound size={16} /> 修改密码</button>
|
|
||||||
<Link to="/prompt-management" onClick={() => setDropdownOpen(false)}><BookText size={16} /> 提示词仓库</Link>
|
|
||||||
{user.role_id === 1 && (
|
|
||||||
<Link to="/admin/management" onClick={() => setDropdownOpen(false)}><Shield size={16} /> 平台管理</Link>
|
|
||||||
)}
|
|
||||||
<button onClick={onLogout}><LogOut size={16} /> 退出登录</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
}
|
||||||
</div>
|
items={[
|
||||||
|
{
|
||||||
|
icon: <KeyRound size={16} />,
|
||||||
|
label: '修改密码',
|
||||||
|
onClick: () => setShowChangePasswordModal(true)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <BookText size={16} />,
|
||||||
|
label: '提示词仓库',
|
||||||
|
onClick: () => window.location.href = '/prompt-management'
|
||||||
|
},
|
||||||
|
...(user.role_id === 1 ? [{
|
||||||
|
icon: <Shield size={16} />,
|
||||||
|
label: '平台管理',
|
||||||
|
onClick: () => window.location.href = '/admin/management'
|
||||||
|
}] : []),
|
||||||
|
{
|
||||||
|
icon: <LogOut size={16} />,
|
||||||
|
label: '退出登录',
|
||||||
|
onClick: onLogout
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
align="right"
|
||||||
|
className="user-menu-dropdown"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -484,13 +487,12 @@ const Dashboard = ({ user, onLogout }) => {
|
||||||
{/* 搜索和标签过滤卡片 */}
|
{/* 搜索和标签过滤卡片 */}
|
||||||
<div className="filter-card-wrapper">
|
<div className="filter-card-wrapper">
|
||||||
<div className="filter-card-search">
|
<div className="filter-card-search">
|
||||||
<ExpandSearchBox
|
<SimpleSearchInput
|
||||||
searchQuery={searchQuery}
|
value={searchQuery}
|
||||||
onSearchChange={setSearchQuery}
|
onChange={setSearchQuery}
|
||||||
placeholder="搜索会议名称或发起人..."
|
placeholder="搜索会议名称或发起人..."
|
||||||
collapsedText="会议搜索"
|
|
||||||
realTimeSearch={true}
|
realTimeSearch={true}
|
||||||
showIcon={true}
|
debounceDelay={500}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import ContentViewer from '../components/ContentViewer';
|
||||||
import TagDisplay from '../components/TagDisplay';
|
import TagDisplay from '../components/TagDisplay';
|
||||||
import Toast from '../components/Toast';
|
import Toast from '../components/Toast';
|
||||||
import ConfirmDialog from '../components/ConfirmDialog';
|
import ConfirmDialog from '../components/ConfirmDialog';
|
||||||
|
import SimpleSearchInput from '../components/SimpleSearchInput';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
|
|
@ -836,12 +837,12 @@ const KnowledgeBasePage = ({ user }) => {
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
{/* 紧凑的搜索和过滤区 */}
|
{/* 紧凑的搜索和过滤区 */}
|
||||||
<div className="search-filter-area">
|
<div className="search-filter-area">
|
||||||
<input
|
<SimpleSearchInput
|
||||||
type="text"
|
|
||||||
placeholder="搜索会议名称或创建人..."
|
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={setSearchQuery}
|
||||||
className="compact-search-input"
|
placeholder="搜索会议名称或创建人..."
|
||||||
|
realTimeSearch={true}
|
||||||
|
debounceDelay={500}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{availableTags.length > 0 && (
|
{availableTags.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -1526,8 +1526,8 @@
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn span {
|
.action-btn.icon-only span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,25 @@
|
||||||
margin-bottom: 40px;
|
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 {
|
.summary-content {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
|
|
@ -157,6 +176,48 @@
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: 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 样式 */
|
/* Markdown 样式 */
|
||||||
|
|
@ -333,8 +394,40 @@
|
||||||
font-size: 14px;
|
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 {
|
.summary-content {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
padding: 15px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-content h1 {
|
.summary-content h1 {
|
||||||
|
|
@ -385,6 +478,11 @@
|
||||||
.preview-title {
|
.preview-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 思维导图平板优化 */
|
||||||
|
.mindmap-wrapper {
|
||||||
|
height: 550px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 打印样式优化 */
|
/* 打印样式优化 */
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,12 @@ import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
import rehypeSanitize from 'rehype-sanitize';
|
import rehypeSanitize from 'rehype-sanitize';
|
||||||
|
import { Tabs } from 'antd';
|
||||||
|
import MindMap from '../components/MindMap';
|
||||||
import './MeetingPreview.css';
|
import './MeetingPreview.css';
|
||||||
|
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
const MeetingPreview = () => {
|
const MeetingPreview = () => {
|
||||||
const { meeting_id } = useParams();
|
const { meeting_id } = useParams();
|
||||||
const [meetingData, setMeetingData] = useState(null);
|
const [meetingData, setMeetingData] = useState(null);
|
||||||
|
|
@ -20,8 +24,8 @@ const MeetingPreview = () => {
|
||||||
const fetchMeetingPreviewData = async () => {
|
const fetchMeetingPreviewData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000';
|
// Use relative path to work in both dev and production
|
||||||
const response = await fetch(`${apiUrl}/api/meetings/${meeting_id}/preview-data`);
|
const response = await fetch(`/api/meetings/${meeting_id}/preview-data`);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.code === "200") {
|
if (result.code === "200") {
|
||||||
|
|
@ -132,15 +136,28 @@ const MeetingPreview = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="summary-section">
|
<div className="summary-section">
|
||||||
<h2 className="section-title">📝 会议摘要</h2>
|
<h2 className="section-title">📝 会议内容</h2>
|
||||||
<div className="summary-content">
|
<Tabs defaultActiveKey="summary" className="preview-tabs">
|
||||||
<ReactMarkdown
|
<TabPane tab="摘要" key="summary">
|
||||||
remarkPlugins={[remarkGfm]}
|
<div className="summary-content">
|
||||||
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
<ReactMarkdown
|
||||||
>
|
remarkPlugins={[remarkGfm]}
|
||||||
{meetingData.summary}
|
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
||||||
</ReactMarkdown>
|
>
|
||||||
</div>
|
{meetingData.summary}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="脑图" key="mindmap">
|
||||||
|
<div className="mindmap-wrapper">
|
||||||
|
<MindMap
|
||||||
|
content={meetingData.summary}
|
||||||
|
title={meetingData.title}
|
||||||
|
initialScale={1.8}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="preview-footer">
|
<div className="preview-footer">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue