From 5a5f71d465736a8352d7e64cd2d349a8c8c8c735 Mon Sep 17 00:00:00 2001 From: alanpaine Date: Thu, 16 Apr 2026 13:05:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BC=9A=E8=AE=AE=E9=A2=84=E8=A7=88):=20?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=88=86=E4=BA=AB=E5=BC=B9=E7=AA=97=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=B9=B6=E6=B7=BB=E5=8A=A0=E9=9F=B3=E9=A2=91=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为会议分享弹窗添加渐变背景和卡片样式 - 在会议预览页面添加自定义音频播放器,支持播放/暂停、进度控制和倍速切换 - 优化转录文本的显示布局,将发言人头像和时间信息整合到内容区域 - 更新免责声明文本,明确AI生成内容的性质 - 调整会议详情页的关键词选择交互,使用勾选图标替代复选框 - 统一多个页面的配色方案,使用主色调变量 --- frontend/src/index.css | 126 ++++- frontend/src/pages/business/MeetingDetail.tsx | 245 ++++---- .../src/pages/business/MeetingPreview.css | 527 +++++++++++------- .../src/pages/business/MeetingPreview.tsx | 154 ++++- 4 files changed, 717 insertions(+), 335 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 60f50a1..7e2722f 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -873,7 +873,131 @@ body::after { margin-inline-start: 12px; } -/* 智能会议专属全局加载动画 */ +.meeting-share-popover .ant-popover-inner { + padding: 0; + border-radius: 16px; + overflow: hidden; +} + +.meeting-share-card { + position: relative; + display: flex; + flex-direction: column; + gap: 16px; + width: 320px; + padding: 20px; + border-radius: 16px; + box-shadow: none; + border: none; + background: var(--app-bg-main); + overflow: hidden; + z-index: 1; +} +.meeting-share-card::before { + content: ""; + position: absolute; + top: -40px; + right: -40px; + width: 120px; + height: 120px; + border-radius: 50%; + background: radial-gradient(circle, rgba(22, 119, 255, 0.15) 0%, transparent 70%); + z-index: -1; + pointer-events: none; +} +.meeting-share-card::after { + content: ""; + position: absolute; + bottom: -40px; + left: -40px; + width: 100px; + height: 100px; + border-radius: 30%; + background: radial-gradient(circle, rgba(54, 207, 201, 0.15) 0%, transparent 70%); + z-index: -1; + pointer-events: none; +} + +.meeting-share-settings { + display: flex; + flex-direction: column; + gap: 12px; + padding-bottom: 16px; + border-bottom: 1px dashed var(--app-border-color); +} + +.meeting-share-settings-row { + display: flex; + align-items: center; + justify-content: space-between; +} + +.meeting-share-settings-copy { + display: flex; + flex-direction: column; + font-size: 13px; +} + +.meeting-share-settings-copy strong { + color: var(--app-text-main); + font-weight: 600; + margin-bottom: 4px; +} + +.meeting-share-settings-copy span { + color: var(--app-text-secondary); + font-size: 12px; +} + +.meeting-share-settings-actions { + display: flex; + gap: 8px; +} + +.meeting-share-qr-wrap { + display: flex; + justify-content: center; + align-items: center; + background: #ffffff; + padding: 16px; + border-radius: 12px; + border: 1px solid var(--app-border-color); +} + +.meeting-share-caption { + font-size: 12px; + color: var(--app-text-secondary); + text-align: center; + line-height: 1.5; +} + +.meeting-share-link-box { + display: flex; + align-items: center; + gap: 8px; + padding: 10px; + background: var(--app-bg-surface); + border-radius: 8px; + border: 1px solid var(--app-border-color); + font-size: 12px; + color: var(--app-text-main); +} + +.meeting-share-link-box span { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.meeting-share-actions { + display: flex; + gap: 12px; +} + +.meeting-share-actions .ant-btn { + flex: 1; +} .ai-meeting-loader { position: fixed; inset: 0; diff --git a/frontend/src/pages/business/MeetingDetail.tsx b/frontend/src/pages/business/MeetingDetail.tsx index 18d67f7..500b4f3 100644 --- a/frontend/src/pages/business/MeetingDetail.tsx +++ b/frontend/src/pages/business/MeetingDetail.tsx @@ -17,6 +17,8 @@ import { RobotOutlined, SyncOutlined, UserOutlined, + PlusOutlined, + CheckCircleFilled, } from '@ant-design/icons'; import dayjs from 'dayjs'; import ReactMarkdown from 'react-markdown'; @@ -473,60 +475,62 @@ const ActiveTranscriptRow = React.memo(({ return ( onSeek(item.startTime)}>
-
- } className="transcript-avatar" /> - {isOwner ? ( - - )} - title="编辑发言人" - trigger="click" + } className="transcript-avatar" /> +
+
+ {isOwner ? ( + + )} + title="编辑发言人" + trigger="click" + > + event.stopPropagation()}> + {item.speakerName || item.speakerId || '发言人'} + + + ) : ( + {item.speakerName || item.speakerId || '发言人'} + )} + {formatTime(item.startTime)} + {speakerTagLabel && {speakerTagLabel}} +
+ {isEditing ? ( +
event.stopPropagation()} > - event.stopPropagation()}> - {item.speakerName || item.speakerId || '发言人'} - - + setDraftValue(event.target.value)} + onKeyDown={(event) => onDraftKeyDown(item, draftValue, event)} + onBlur={(event) => { + event.stopPropagation(); + onDraftBlur(item, draftValue); + }} + autoSize={{ minRows: 1, maxRows: 8 }} + className="transcript-bubble-input" + bordered={false} + /> +
) : ( - {item.speakerName || item.speakerId || '发言人'} +
onStartEdit(item, event) : undefined} + > + {item.content} +
)} - {formatTime(item.startTime)} - {speakerTagLabel && {speakerTagLabel}}
- {isEditing ? ( -
event.stopPropagation()} - > - setDraftValue(event.target.value)} - onKeyDown={(event) => onDraftKeyDown(item, draftValue, event)} - onBlur={(event) => { - event.stopPropagation(); - onDraftBlur(item, draftValue); - }} - autoSize={{ minRows: 1, maxRows: 8 }} - className="transcript-bubble-input" - bordered={false} - /> -
- ) : ( -
onStartEdit(item, event) : undefined} - > - {item.content} -
- )}
); @@ -1118,8 +1122,8 @@ const MeetingDetail: React.FC = () => { value={sharePreviewUrl} type="svg" size={172} - color="#315f8b" - bgColor="#fffaf4" + color="#1677ff" + bgColor="transparent" errorLevel="H" bordered={false} /> @@ -1254,18 +1258,6 @@ const MeetingDetail: React.FC = () => {
智能速览 - {isOwner && analysis.keywords.length > 0 && ( - - )}
{hasAnalysis ? '已生成' : '待生成'} @@ -1275,18 +1267,36 @@ const MeetingDetail: React.FC = () => {
关键词 + {isOwner && analysis.keywords.length > 0 && ( + + )}
{analysis.keywords.length ? ( <>
- {visibleKeywords.map((tag) => ( - - ))} + {visibleKeywords.map((tag) => { + const isSelected = selectedKeywords.includes(tag); + return ( +
isOwner && handleKeywordToggle(tag, !isSelected)} + > + {tag} + {isOwner && isSelected && } +
+ ); + })}
{analysis.keywords.length > 9 && (
- {meeting.duration ? formatDurationRange(0, meeting.duration) : TEXT.noDuration} + {meetingDuration > 0 ? formatDurationRange(0, meetingDuration) : TEXT.noDuration}
@@ -713,21 +777,10 @@ export default function MeetingPreview() { /> ) : null} - {meeting.audioUrl ? ( -