feat(会议预览): 增强分享弹窗样式并添加音频播放器
- 为会议分享弹窗添加渐变背景和卡片样式 - 在会议预览页面添加自定义音频播放器,支持播放/暂停、进度控制和倍速切换 - 优化转录文本的显示布局,将发言人头像和时间信息整合到内容区域 - 更新免责声明文本,明确AI生成内容的性质 - 调整会议详情页的关键词选择交互,使用勾选图标替代复选框 - 统一多个页面的配色方案,使用主色调变量dev_na
parent
7c64cdf7a2
commit
5a5f71d465
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<ActiveTranscriptRowProps>(({
|
|||
return (
|
||||
<List.Item className="transcript-row" onClick={() => onSeek(item.startTime)}>
|
||||
<div className="transcript-entry">
|
||||
<div className="transcript-meta">
|
||||
<Avatar icon={<UserOutlined />} className="transcript-avatar" />
|
||||
{isOwner ? (
|
||||
<Popover
|
||||
content={(
|
||||
<SpeakerEditor
|
||||
meetingId={meetingId}
|
||||
speakerId={item.speakerId}
|
||||
initialName={item.speakerName}
|
||||
initialLabel={item.speakerLabel}
|
||||
onSuccess={onSpeakerUpdated}
|
||||
/>
|
||||
)}
|
||||
title="编辑发言人"
|
||||
trigger="click"
|
||||
<Avatar icon={<UserOutlined />} className="transcript-avatar" />
|
||||
<div className="transcript-content-wrap">
|
||||
<div className="transcript-meta">
|
||||
{isOwner ? (
|
||||
<Popover
|
||||
content={(
|
||||
<SpeakerEditor
|
||||
meetingId={meetingId}
|
||||
speakerId={item.speakerId}
|
||||
initialName={item.speakerName}
|
||||
initialLabel={item.speakerLabel}
|
||||
onSuccess={onSpeakerUpdated}
|
||||
/>
|
||||
)}
|
||||
title="编辑发言人"
|
||||
trigger="click"
|
||||
>
|
||||
<span className="transcript-speaker editable" onClick={(event) => event.stopPropagation()}>
|
||||
{item.speakerName || item.speakerId || '发言人'} <EditOutlined style={{ fontSize: 12 }} />
|
||||
</span>
|
||||
</Popover>
|
||||
) : (
|
||||
<span className="transcript-speaker">{item.speakerName || item.speakerId || '发言人'}</span>
|
||||
)}
|
||||
<Text type="secondary">{formatTime(item.startTime)}</Text>
|
||||
{speakerTagLabel && <Tag color="blue">{speakerTagLabel}</Tag>}
|
||||
</div>
|
||||
{isEditing ? (
|
||||
<div
|
||||
className={`transcript-bubble transcript-bubble-editing ${isSaving ? 'is-saving' : ''}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<span className="transcript-speaker editable" onClick={(event) => event.stopPropagation()}>
|
||||
{item.speakerName || item.speakerId || '发言人'} <EditOutlined style={{ fontSize: 12 }} />
|
||||
</span>
|
||||
</Popover>
|
||||
<Input.TextArea
|
||||
autoFocus
|
||||
value={draftValue}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(event) => 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}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="transcript-speaker">{item.speakerName || item.speakerId || '发言人'}</span>
|
||||
<div
|
||||
className={`transcript-bubble ${isOwner ? 'editable' : ''}`}
|
||||
onDoubleClick={isOwner ? (event) => onStartEdit(item, event) : undefined}
|
||||
>
|
||||
{item.content}
|
||||
</div>
|
||||
)}
|
||||
<Text type="secondary">{formatTime(item.startTime)}</Text>
|
||||
{speakerTagLabel && <Tag color="blue">{speakerTagLabel}</Tag>}
|
||||
</div>
|
||||
{isEditing ? (
|
||||
<div
|
||||
className={`transcript-bubble transcript-bubble-editing ${isSaving ? 'is-saving' : ''}`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoFocus
|
||||
value={draftValue}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(event) => 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}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`transcript-bubble ${isOwner ? 'editable' : ''}`}
|
||||
onDoubleClick={isOwner ? (event) => onStartEdit(item, event) : undefined}
|
||||
>
|
||||
{item.content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</List.Item>
|
||||
);
|
||||
|
|
@ -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 = () => {
|
|||
<div className="summary-title">
|
||||
<RobotOutlined />
|
||||
<span>智能速览</span>
|
||||
{isOwner && analysis.keywords.length > 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
ghost
|
||||
disabled={!selectedKeywords.length}
|
||||
loading={addingHotwords}
|
||||
onClick={handleAddSelectedHotwords}
|
||||
>
|
||||
一键加入热词
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="summary-actions">
|
||||
<span className={`status-pill ${hasAnalysis ? 'success' : 'warning'}`}>{hasAnalysis ? '已生成' : '待生成'}</span>
|
||||
|
|
@ -1275,18 +1267,36 @@ const MeetingDetail: React.FC = () => {
|
|||
<div className="summary-block">
|
||||
<div className="summary-section-head">
|
||||
<span>关键词</span>
|
||||
{isOwner && analysis.keywords.length > 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
shape="round"
|
||||
icon={<PlusOutlined />}
|
||||
disabled={!selectedKeywords.length}
|
||||
loading={addingHotwords}
|
||||
onClick={handleAddSelectedHotwords}
|
||||
>
|
||||
加入热词 {selectedKeywords.length > 0 ? `(${selectedKeywords.length})` : ''}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{analysis.keywords.length ? (
|
||||
<>
|
||||
<div className="record-tags">
|
||||
{visibleKeywords.map((tag) => (
|
||||
<label key={tag} className={`tag selectable-tag ${selectedKeywords.includes(tag) ? 'selected' : ''}`}>
|
||||
{isOwner && (
|
||||
<Checkbox checked={selectedKeywords.includes(tag)} onChange={(event) => handleKeywordToggle(tag, event.target.checked)} />
|
||||
)}
|
||||
<span>{tag}</span>
|
||||
</label>
|
||||
))}
|
||||
{visibleKeywords.map((tag) => {
|
||||
const isSelected = selectedKeywords.includes(tag);
|
||||
return (
|
||||
<div
|
||||
key={tag}
|
||||
className={`tag selectable-tag ${isSelected ? 'selected' : ''}`}
|
||||
onClick={() => isOwner && handleKeywordToggle(tag, !isSelected)}
|
||||
>
|
||||
<span>{tag}</span>
|
||||
{isOwner && isSelected && <CheckCircleFilled style={{ fontSize: 12 }} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{analysis.keywords.length > 9 && (
|
||||
<button type="button" className="summary-link" onClick={() => setExpandKeywords((value) => !value)}>
|
||||
|
|
@ -1765,23 +1775,33 @@ const MeetingDetail: React.FC = () => {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(107, 115, 255, 0.08);
|
||||
color: #6a72ff;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 119, 255, 0.06);
|
||||
color: var(--app-primary-color);
|
||||
border: 1px solid rgba(22, 119, 255, 0.15);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
}
|
||||
.selectable-tag {
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
.selectable-tag:hover {
|
||||
background: rgba(22, 119, 255, 0.12);
|
||||
border-color: rgba(22, 119, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.selectable-tag.selected {
|
||||
border-color: rgba(106, 114, 255, 0.28);
|
||||
background: rgba(107, 115, 255, 0.14);
|
||||
background: linear-gradient(135deg, var(--app-primary-color), #36cfc9);
|
||||
color: #ffffff;
|
||||
border-color: transparent;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.24);
|
||||
}
|
||||
.selectable-tag .ant-checkbox {
|
||||
margin-inline-end: 2px;
|
||||
.selectable-tag.selected:hover {
|
||||
box-shadow: 0 6px 16px rgba(22, 119, 255, 0.35);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.summary-copy {
|
||||
color: #465072;
|
||||
|
|
@ -1859,8 +1879,7 @@ const MeetingDetail: React.FC = () => {
|
|||
padding-top: 12px;
|
||||
}
|
||||
.chapter-item {
|
||||
display: grid;
|
||||
grid-template-columns: 64px minmax(0, 1fr);
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
|
@ -1870,6 +1889,8 @@ const MeetingDetail: React.FC = () => {
|
|||
color: #58627f;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
width: 64px;
|
||||
}
|
||||
.chapter-time::after {
|
||||
content: "";
|
||||
|
|
@ -1942,14 +1963,14 @@ const MeetingDetail: React.FC = () => {
|
|||
font-size: 12px;
|
||||
}
|
||||
.keypoint-card {
|
||||
display: grid;
|
||||
grid-template-columns: 48px minmax(0, 1fr);
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
}
|
||||
.keypoint-badge {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 12px;
|
||||
background: rgba(107, 115, 255, 0.12);
|
||||
color: #5c64ff;
|
||||
|
|
@ -2012,12 +2033,18 @@ const MeetingDetail: React.FC = () => {
|
|||
justify-self: start;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.transcript-content-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.transcript-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -2026,14 +2053,17 @@ const MeetingDetail: React.FC = () => {
|
|||
color: #8e98b8;
|
||||
}
|
||||
.transcript-avatar {
|
||||
background: linear-gradient(135deg, #7a84ff, #9363ff) !important;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(135deg, #1677ff, #36cfc9) !important;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.transcript-speaker {
|
||||
color: #5e698d;
|
||||
color: var(--app-text-main);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
.transcript-speaker.editable {
|
||||
color: #6470ff;
|
||||
color: var(--app-primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.transcript-bubble {
|
||||
|
|
@ -2043,13 +2073,18 @@ const MeetingDetail: React.FC = () => {
|
|||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 14px 18px;
|
||||
border-radius: 16px;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(234, 238, 248, 1);
|
||||
box-shadow: 0 12px 28px rgba(137, 149, 193, 0.08);
|
||||
color: #3f496a;
|
||||
line-height: 1.86;
|
||||
border-radius: 4px 16px 16px 16px;
|
||||
background: var(--app-bg-surface);
|
||||
border: 1px solid var(--app-border-color);
|
||||
color: var(--app-text-main);
|
||||
line-height: 1.6;
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.transcript-bubble:hover {
|
||||
border-color: var(--app-primary-color);
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.05);
|
||||
}
|
||||
.transcript-bubble.editable {
|
||||
cursor: text;
|
||||
|
|
@ -2113,9 +2148,9 @@ const MeetingDetail: React.FC = () => {
|
|||
margin-top: 18px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(249, 250, 255, 0.98), rgba(239, 242, 255, 0.98));
|
||||
border: 1px solid rgba(224, 229, 247, 0.98);
|
||||
box-shadow: 0 14px 30px rgba(145, 158, 212, 0.18);
|
||||
background: var(--app-bg-surface);
|
||||
border: 1px solid var(--app-border-color);
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.08);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
.player-main-btn,
|
||||
|
|
@ -2130,18 +2165,18 @@ const MeetingDetail: React.FC = () => {
|
|||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6e75ff, #8268ff);
|
||||
background: linear-gradient(135deg, var(--app-primary-color), #36cfc9);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
box-shadow: 0 12px 24px rgba(108, 117, 255, 0.28);
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
.player-ghost-btn {
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
background: rgba(107, 115, 255, 0.08);
|
||||
color: #5e67ff;
|
||||
background: var(--app-bg-surface-soft);
|
||||
color: var(--app-primary-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
.player-progress-shell {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
.meeting-preview-page {
|
||||
--preview-bg:
|
||||
radial-gradient(circle at top left, rgba(252, 208, 157, 0.24), transparent 32%),
|
||||
radial-gradient(circle at top right, rgba(82, 164, 255, 0.18), transparent 28%),
|
||||
linear-gradient(180deg, #fffaf4 0%, #f8f1e7 52%, #efe6da 100%);
|
||||
--preview-ink: #33261c;
|
||||
--preview-muted: rgba(72, 53, 39, 0.72);
|
||||
--preview-line: rgba(84, 57, 31, 0.12);
|
||||
--preview-card: rgba(255, 250, 244, 0.74);
|
||||
--preview-card-strong: rgba(255, 252, 247, 0.88);
|
||||
--preview-shadow: 0 24px 64px rgba(105, 72, 40, 0.12);
|
||||
--preview-accent: #b86432;
|
||||
--preview-accent-soft: rgba(184, 100, 50, 0.14);
|
||||
--preview-cool: #315f8b;
|
||||
--preview-bg: var(--app-bg-main);
|
||||
--preview-ink: var(--app-text-main);
|
||||
--preview-muted: var(--app-text-secondary);
|
||||
--preview-line: var(--app-border-color);
|
||||
--preview-card: var(--app-bg-card);
|
||||
--preview-card-strong: var(--app-bg-surface-soft);
|
||||
--preview-shadow: var(--app-shadow-lg);
|
||||
--preview-accent: var(--app-primary-color);
|
||||
--preview-accent-soft: rgba(22, 119, 255, 0.1);
|
||||
--preview-cool: #1677ff;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 24px 16px 40px;
|
||||
|
|
@ -36,7 +33,7 @@
|
|||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle, rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0) 72%),
|
||||
radial-gradient(circle at 36% 36%, rgba(184, 100, 50, 0.18), rgba(184, 100, 50, 0) 56%);
|
||||
radial-gradient(circle at 36% 36%, rgba(22, 119, 255, 0.18), rgba(22, 119, 255, 0) 56%);
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
|
|
@ -46,9 +43,9 @@
|
|||
width: 160px;
|
||||
height: 160px;
|
||||
border-radius: 32px;
|
||||
border: 1px solid rgba(49, 95, 139, 0.14);
|
||||
border: 1px solid rgba(22, 119, 255, 0.14);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(49, 95, 139, 0.12), rgba(49, 95, 139, 0)),
|
||||
linear-gradient(135deg, rgba(22, 119, 255, 0.12), rgba(22, 119, 255, 0)),
|
||||
rgba(255, 255, 255, 0.32);
|
||||
transform: rotate(16deg);
|
||||
}
|
||||
|
|
@ -70,19 +67,9 @@
|
|||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--preview-line);
|
||||
border-radius: 28px;
|
||||
border-radius: 24px;
|
||||
background: var(--preview-card);
|
||||
box-shadow: var(--preview-shadow);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.meeting-preview-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(120deg, rgba(255, 255, 255, 0.66), rgba(255, 255, 255, 0.1) 42%, transparent 70%);
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.meeting-preview-hero,
|
||||
|
|
@ -110,8 +97,8 @@
|
|||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
background: var(--app-bg-surface);
|
||||
border: 1px solid var(--preview-line);
|
||||
color: var(--preview-muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.12em;
|
||||
|
|
@ -129,18 +116,18 @@
|
|||
}
|
||||
|
||||
.meeting-preview-status.is-complete {
|
||||
background: rgba(59, 130, 89, 0.12);
|
||||
color: #1f6a43;
|
||||
background: rgba(82, 196, 26, 0.12);
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.meeting-preview-status.is-processing {
|
||||
background: rgba(49, 95, 139, 0.12);
|
||||
color: var(--preview-cool);
|
||||
background: rgba(22, 119, 255, 0.12);
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.meeting-preview-status.is-warning {
|
||||
background: rgba(184, 100, 50, 0.12);
|
||||
color: var(--preview-accent);
|
||||
background: rgba(250, 173, 20, 0.12);
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.meeting-preview-title {
|
||||
|
|
@ -186,7 +173,7 @@
|
|||
padding: 14px 14px 12px;
|
||||
border-radius: 18px;
|
||||
background: var(--preview-card-strong);
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
border: 1px solid var(--preview-line);
|
||||
}
|
||||
|
||||
.meeting-preview-metric-label {
|
||||
|
|
@ -228,13 +215,63 @@
|
|||
}
|
||||
|
||||
.meeting-preview-password-gate {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
max-width: 480px;
|
||||
margin: 10vh auto;
|
||||
padding: 40px 32px;
|
||||
text-align: center;
|
||||
border-radius: 24px;
|
||||
background: var(--preview-card);
|
||||
border: 1px solid var(--preview-line);
|
||||
box-shadow: var(--preview-shadow);
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.meeting-preview-password-gate .meeting-preview-section-header {
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.meeting-preview-password-gate .meeting-preview-section-kicker {
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.meeting-preview-password-gate .meeting-preview-section-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.meeting-preview-password-form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 28px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.meeting-preview-password-form .ant-input-affix-wrapper {
|
||||
padding: 10px 16px;
|
||||
border-radius: 12px;
|
||||
background: var(--app-bg-surface-soft);
|
||||
border-color: var(--preview-line);
|
||||
}
|
||||
|
||||
.meeting-preview-password-form .ant-input-affix-wrapper-focused {
|
||||
border-color: var(--preview-accent);
|
||||
box-shadow: 0 0 0 2px var(--preview-accent-soft);
|
||||
background: var(--app-bg-surface);
|
||||
}
|
||||
|
||||
.meeting-preview-password-form .ant-input {
|
||||
background: transparent;
|
||||
font-size: 16px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.meeting-preview-password-form .ant-btn {
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.meeting-preview-section-header {
|
||||
|
|
@ -284,7 +321,7 @@
|
|||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--preview-card-strong);
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
border: 1px solid var(--preview-line);
|
||||
color: var(--preview-ink);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
|
@ -293,18 +330,19 @@
|
|||
margin-top: 16px;
|
||||
padding: 18px;
|
||||
border-radius: 22px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.68), rgba(255, 255, 255, 0.4)),
|
||||
rgba(255, 255, 255, 0.32);
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
background: var(--app-bg-surface-soft);
|
||||
border: 1px solid var(--preview-line);
|
||||
}
|
||||
|
||||
.meeting-preview-overview-label {
|
||||
margin-bottom: 8px;
|
||||
color: var(--preview-muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
.meeting-preview-disclaimer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 32px;
|
||||
padding: 16px;
|
||||
color: var(--app-text-secondary);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.meeting-preview-overview-copy {
|
||||
|
|
@ -323,7 +361,7 @@
|
|||
width: 100%;
|
||||
padding: 5px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
background: var(--app-bg-surface-soft);
|
||||
}
|
||||
|
||||
.meeting-preview-analysis-tabs .ant-segmented-item {
|
||||
|
|
@ -344,30 +382,32 @@
|
|||
.meeting-preview-keypoint,
|
||||
.meeting-preview-speaker-card,
|
||||
.meeting-preview-todo {
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
border: 1px solid var(--preview-line);
|
||||
background: var(--preview-card-strong);
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.meeting-preview-chapter,
|
||||
.meeting-preview-keypoint {
|
||||
display: grid;
|
||||
grid-template-columns: 70px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.meeting-preview-chapter-time,
|
||||
.meeting-preview-keypoint-index {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 8px;
|
||||
border-radius: 16px;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 14px;
|
||||
background: var(--preview-accent-soft);
|
||||
color: var(--preview-accent);
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-item-title {
|
||||
|
|
@ -397,8 +437,8 @@
|
|||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(49, 95, 139, 0.1);
|
||||
color: var(--preview-cool);
|
||||
background: var(--preview-accent-soft);
|
||||
color: var(--preview-accent);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
@ -414,222 +454,307 @@
|
|||
}
|
||||
|
||||
.meeting-preview-speaker-avatar {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: rgba(49, 95, 139, 0.14);
|
||||
color: var(--preview-cool);
|
||||
border-radius: 12px;
|
||||
background: var(--preview-accent-soft);
|
||||
color: var(--preview-accent);
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-speaker-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.meeting-preview-speaker-role {
|
||||
margin-top: 2px;
|
||||
color: var(--preview-muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.meeting-preview-todo {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 16px;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-todo-dot {
|
||||
flex: none;
|
||||
flex-shrink: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-top: 8px;
|
||||
margin-top: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--preview-accent);
|
||||
box-shadow: 0 0 0 6px rgba(184, 100, 50, 0.12);
|
||||
border: 2px solid var(--preview-accent);
|
||||
box-shadow: 0 0 0 6px var(--preview-accent-soft);
|
||||
}
|
||||
|
||||
.meeting-preview-markdown {
|
||||
padding: 4px 0 0;
|
||||
}
|
||||
|
||||
.meeting-preview-markdown .ant-empty {
|
||||
padding: 24px 0 8px;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
color: var(--preview-ink);
|
||||
}
|
||||
|
||||
.meeting-preview-markdown h1,
|
||||
.meeting-preview-markdown h2,
|
||||
.meeting-preview-markdown h3,
|
||||
.meeting-preview-markdown h4 {
|
||||
margin-top: 1.4em;
|
||||
margin-bottom: 0.7em;
|
||||
font-family: Georgia, "Times New Roman", "Songti SC", serif;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.03em;
|
||||
.meeting-preview-markdown h3 {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 700;
|
||||
color: var(--preview-ink);
|
||||
}
|
||||
|
||||
.meeting-preview-markdown p,
|
||||
.meeting-preview-markdown li {
|
||||
color: var(--preview-ink);
|
||||
font-size: 15px;
|
||||
line-height: 1.92;
|
||||
.meeting-preview-markdown p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.meeting-preview-markdown ul,
|
||||
.meeting-preview-markdown ol {
|
||||
margin-bottom: 1em;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.meeting-preview-markdown blockquote {
|
||||
margin: 18px 0;
|
||||
padding: 12px 16px;
|
||||
border-left: 3px solid rgba(184, 100, 50, 0.4);
|
||||
border-radius: 0 16px 16px 0;
|
||||
background: rgba(184, 100, 50, 0.06);
|
||||
}
|
||||
|
||||
.meeting-preview-markdown pre {
|
||||
overflow: auto;
|
||||
padding: 14px;
|
||||
border-radius: 18px;
|
||||
background: rgba(51, 38, 28, 0.92);
|
||||
}
|
||||
|
||||
.meeting-preview-markdown code {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-audio {
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
margin: 0 0 1em;
|
||||
padding: 10px 16px;
|
||||
border-left: 3px solid var(--preview-accent);
|
||||
border-radius: 0 8px 8px 0;
|
||||
background: var(--preview-accent-soft);
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-item {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(84, 57, 31, 0.08);
|
||||
border-radius: 22px;
|
||||
background: var(--preview-card-strong);
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
background 0.2s ease;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-item:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(184, 100, 50, 0.22);
|
||||
box-shadow: 0 12px 26px rgba(105, 72, 40, 0.08);
|
||||
background: var(--app-bg-surface-soft);
|
||||
border-color: var(--preview-line);
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-item.is-active {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(184, 100, 50, 0.28);
|
||||
box-shadow: 0 12px 26px rgba(105, 72, 40, 0.08);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(255, 244, 235, 0.92));
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-item.is-active .meeting-preview-transcript-copy {
|
||||
color: #5a3113;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-speaker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
background: var(--app-bg-surface);
|
||||
border-color: var(--preview-accent);
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.08);
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-avatar {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #1677ff, #36cfc9);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-name {
|
||||
.meeting-preview-transcript-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-speaker {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
color: var(--app-text-main);
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-time {
|
||||
flex: none;
|
||||
color: var(--preview-muted);
|
||||
font-size: 12px;
|
||||
color: var(--app-text-secondary);
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-copy {
|
||||
color: var(--preview-ink);
|
||||
font-size: 14px;
|
||||
line-height: 1.82;
|
||||
.meeting-preview-transcript-text {
|
||||
padding: 14px 18px;
|
||||
border-radius: 4px 16px 16px 16px;
|
||||
background: var(--app-bg-surface);
|
||||
border: 1px solid var(--app-border-color);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: var(--app-text-main);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.meeting-preview-disclaimer {
|
||||
margin-top: 18px;
|
||||
padding: 0 8px;
|
||||
color: rgba(72, 53, 39, 0.64);
|
||||
.meeting-preview-transcript-item.is-active .meeting-preview-transcript-text {
|
||||
border-color: var(--preview-accent-soft);
|
||||
background: #f0f5ff;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-text mark {
|
||||
background: var(--preview-accent-soft);
|
||||
color: var(--preview-accent);
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.meeting-preview-page .transcript-player {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: min(calc(100% - 32px), 860px);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: var(--app-bg-surface);
|
||||
border: 1px solid var(--app-border-color);
|
||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
.player-main-btn,
|
||||
.player-ghost-btn {
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.player-main-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--app-primary-color), #36cfc9);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
.player-ghost-btn {
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
background: var(--app-bg-surface-soft);
|
||||
color: var(--app-primary-color);
|
||||
font-weight: 700;
|
||||
}
|
||||
.player-progress-shell {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.player-time-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--app-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
.player-range {
|
||||
width: 100%;
|
||||
appearance: none;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--app-primary-color), var(--app-primary-color)) 0/0% 100% no-repeat,
|
||||
var(--app-bg-surface-soft);
|
||||
outline: none;
|
||||
}
|
||||
.player-range::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 3px solid var(--app-primary-color);
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.24);
|
||||
cursor: pointer;
|
||||
}
|
||||
.player-range::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 3px solid var(--app-primary-color);
|
||||
box-shadow: 0 4px 12px rgba(22, 119, 255, 0.24);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes preview-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(18px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
0% { transform: translateY(30px); opacity: 0; }
|
||||
100% { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@media (max-width: 768px) {
|
||||
.meeting-preview-page {
|
||||
padding: 36px 28px 56px;
|
||||
padding: 16px 12px 32px;
|
||||
}
|
||||
|
||||
.meeting-preview-hero,
|
||||
.meeting-preview-section {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
.meeting-preview-hero {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.meeting-preview-metrics {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.meeting-preview-panels {
|
||||
gap: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.meeting-preview-hero-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.meeting-preview-chapter,
|
||||
.meeting-preview-keypoint {
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.meeting-preview-chapter-time,
|
||||
.meeting-preview-keypoint-index {
|
||||
width: auto;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.meeting-preview-transcript-item {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.meeting-preview-audio-player {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.meeting-preview-download-btn {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ import { useParams, useSearchParams } from "react-router-dom";
|
|||
import {
|
||||
AudioOutlined,
|
||||
CalendarOutlined,
|
||||
CaretRightFilled,
|
||||
ClockCircleOutlined,
|
||||
CopyOutlined,
|
||||
FastForwardOutlined,
|
||||
FileTextOutlined,
|
||||
LockOutlined,
|
||||
PauseOutlined,
|
||||
RobotOutlined,
|
||||
ShareAltOutlined,
|
||||
TeamOutlined,
|
||||
|
|
@ -98,7 +101,7 @@ const TEXT = {
|
|||
audioUnavailable: "\u97f3\u9891\u6587\u4ef6\u4e0d\u53ef\u7528\uff0c\u4ec5\u5c55\u793a\u8f6c\u5f55\u5185\u5bb9\u3002",
|
||||
noTranscript: "\u6682\u65e0\u8f6c\u5f55\u5185\u5bb9",
|
||||
unknownSpeaker: "\u672a\u77e5\u53d1\u8a00\u4eba",
|
||||
disclaimer: "\u8be5\u9875\u9762\u4e3a\u4f1a\u8bae\u5206\u4eab\u9884\u89c8\u9875\uff0c\u672a\u5f00\u653e\u7f16\u8f91\u3001\u5bfc\u51fa\u548c\u5176\u4ed6\u53d7\u4fdd\u62a4\u64cd\u4f5c\u3002",
|
||||
disclaimer: "智能内容由用户会议内容 + AI 模型生成,我们不对内容准确性和完整性做任何保证,亦不代表我们的观点或态度",
|
||||
shareText: "\u6211\u5411\u4f60\u5206\u4eab\u4e86\u4e00\u4e2a\u4f1a\u8bae\u9884\u89c8\u94fe\u63a5",
|
||||
audioSaved: "\u5df2\u4fdd\u5b58",
|
||||
audioSaveFailed: "\u4fdd\u5b58\u5931\u8d25",
|
||||
|
|
@ -170,6 +173,10 @@ export default function MeetingPreview() {
|
|||
const [passwordVerified, setPasswordVerified] = useState(false);
|
||||
const [accessPassword, setAccessPassword] = useState("");
|
||||
const [passwordError, setPasswordError] = useState("");
|
||||
const [audioPlaying, setAudioPlaying] = useState(false);
|
||||
const [audioCurrentTime, setAudioCurrentTime] = useState(0);
|
||||
const [audioDuration, setAudioDuration] = useState(0);
|
||||
const [audioPlaybackRate, setAudioPlaybackRate] = useState(1);
|
||||
const [isMobile, setIsMobile] = useState(() =>
|
||||
typeof window !== "undefined" ? window.matchMedia("(max-width: 767px)").matches : false,
|
||||
);
|
||||
|
|
@ -309,6 +316,14 @@ export default function MeetingPreview() {
|
|||
const participantCountValue =
|
||||
isMobile && transcriptSpeakers.length > 0 ? transcriptSpeakers.length : participants.length;
|
||||
|
||||
const meetingDuration = useMemo(() => {
|
||||
if (transcripts.length > 0) {
|
||||
const last = transcripts[transcripts.length - 1];
|
||||
return last.endTime || 0;
|
||||
}
|
||||
return 0;
|
||||
}, [transcripts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeTranscriptId) {
|
||||
return;
|
||||
|
|
@ -331,12 +346,49 @@ export default function MeetingPreview() {
|
|||
audioRef.current.play().catch(() => {});
|
||||
};
|
||||
|
||||
const toggleAudioPlayback = () => {
|
||||
if (!audioRef.current) return;
|
||||
if (audioPlaying) {
|
||||
audioRef.current.pause();
|
||||
} else {
|
||||
audioRef.current.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAudioProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!audioRef.current) return;
|
||||
const time = parseFloat(e.target.value);
|
||||
audioRef.current.currentTime = time;
|
||||
setAudioCurrentTime(time);
|
||||
};
|
||||
|
||||
const cyclePlaybackRate = () => {
|
||||
if (!audioRef.current) return;
|
||||
const nextRate = audioPlaybackRate === 1 ? 1.5 : audioPlaybackRate === 1.5 ? 2 : 1;
|
||||
audioRef.current.playbackRate = nextRate;
|
||||
setAudioPlaybackRate(nextRate);
|
||||
};
|
||||
|
||||
const formatPlayerTime = (seconds: number) => {
|
||||
if (!seconds || isNaN(seconds)) return '00:00';
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleAudioTimeUpdate = () => {
|
||||
if (!audioRef.current || transcripts.length === 0) {
|
||||
return;
|
||||
if (!audioRef.current) return;
|
||||
const currentSeconds = audioRef.current.currentTime;
|
||||
setAudioCurrentTime(currentSeconds);
|
||||
|
||||
// Also update duration if it's available now
|
||||
if (audioRef.current.duration && audioDuration !== audioRef.current.duration) {
|
||||
setAudioDuration(audioRef.current.duration);
|
||||
}
|
||||
|
||||
const currentMs = audioRef.current.currentTime * 1000;
|
||||
if (transcripts.length === 0) return;
|
||||
|
||||
const currentMs = currentSeconds * 1000;
|
||||
const currentItem = transcripts.find(
|
||||
(item) => currentMs >= (item.startTime || 0) && currentMs <= (item.endTime || 0),
|
||||
);
|
||||
|
|
@ -344,6 +396,18 @@ export default function MeetingPreview() {
|
|||
setActiveTranscriptId(currentItem?.id || null);
|
||||
};
|
||||
|
||||
const handleAudioEnded = () => {
|
||||
setAudioPlaying(false);
|
||||
};
|
||||
|
||||
const handleAudioPlay = () => setAudioPlaying(true);
|
||||
const handleAudioPause = () => setAudioPlaying(false);
|
||||
const handleAudioLoadedMetadata = () => {
|
||||
if (audioRef.current) {
|
||||
setAudioDuration(audioRef.current.duration);
|
||||
}
|
||||
};
|
||||
|
||||
const renderMeetingTitle = (title?: string) => {
|
||||
const safeTitle = title || TEXT.untitledMeeting;
|
||||
return safeTitle.split(/(\d+)/).map((part, index) =>
|
||||
|
|
@ -700,7 +764,7 @@ export default function MeetingPreview() {
|
|||
</div>
|
||||
<div className="meeting-preview-section-extra">
|
||||
<ClockCircleOutlined style={{ marginRight: 6 }} />
|
||||
{meeting.duration ? formatDurationRange(0, meeting.duration) : TEXT.noDuration}
|
||||
{meetingDuration > 0 ? formatDurationRange(0, meetingDuration) : TEXT.noDuration}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -713,21 +777,10 @@ export default function MeetingPreview() {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{meeting.audioUrl ? (
|
||||
<audio
|
||||
ref={audioRef}
|
||||
className="meeting-preview-transcript-audio"
|
||||
controls
|
||||
src={meeting.audioUrl}
|
||||
onTimeUpdate={handleAudioTimeUpdate}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="meeting-preview-transcript-list">
|
||||
{transcripts.length > 0 ? (
|
||||
transcripts.map((item) => {
|
||||
const speakerKey = item.speakerName || item.speakerLabel || item.speakerId || "speaker";
|
||||
const avatarColor = transcriptColorSeed(speakerKey);
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
|
|
@ -737,20 +790,20 @@ export default function MeetingPreview() {
|
|||
className={`meeting-preview-transcript-item ${activeTranscriptId === item.id ? "is-active" : ""}`}
|
||||
onClick={() => handleTranscriptSeek(item)}
|
||||
>
|
||||
<div className="meeting-preview-transcript-head">
|
||||
<div className="meeting-preview-transcript-speaker">
|
||||
<div className="meeting-preview-transcript-avatar" style={{ backgroundColor: avatarColor }}>
|
||||
{(speakerKey || "S").slice(0, 1)}
|
||||
</div>
|
||||
<div className="meeting-preview-transcript-name">
|
||||
{item.speakerName || item.speakerLabel || item.speakerId || TEXT.unknownSpeaker}
|
||||
</div>
|
||||
<div className="meeting-preview-transcript-avatar">
|
||||
{(speakerKey || "S").slice(0, 1)}
|
||||
</div>
|
||||
<div className="meeting-preview-transcript-content">
|
||||
<div className="meeting-preview-transcript-meta">
|
||||
<span className="meeting-preview-transcript-speaker">{speakerKey}</span>
|
||||
<span className="meeting-preview-transcript-time">
|
||||
{formatDurationRange(item.startTime, item.endTime)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="meeting-preview-transcript-time">
|
||||
{formatDurationRange(item.startTime, item.endTime)}
|
||||
<div className="meeting-preview-transcript-text">
|
||||
{item.content || TEXT.noTranscript}
|
||||
</div>
|
||||
</div>
|
||||
<div className="meeting-preview-transcript-copy">{item.content || TEXT.noTranscript}</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
|
|
@ -826,10 +879,55 @@ export default function MeetingPreview() {
|
|||
</div>
|
||||
|
||||
<div className="meeting-preview-disclaimer">
|
||||
<UserOutlined style={{ marginRight: 8 }} />
|
||||
<RobotOutlined style={{ marginRight: 8 }} />
|
||||
{TEXT.disclaimer}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{meeting.audioUrl && (
|
||||
<audio
|
||||
ref={audioRef}
|
||||
src={meeting.audioUrl}
|
||||
onTimeUpdate={handleAudioTimeUpdate}
|
||||
onPlay={handleAudioPlay}
|
||||
onPause={handleAudioPause}
|
||||
onEnded={handleAudioEnded}
|
||||
onLoadedMetadata={handleAudioLoadedMetadata}
|
||||
style={{ display: 'none' }}
|
||||
preload="metadata"
|
||||
/>
|
||||
)}
|
||||
|
||||
{meeting.audioUrl && pageTab === 'transcript' ? (
|
||||
<>
|
||||
<div style={{ height: 100, flexShrink: 0, pointerEvents: 'none' }} />
|
||||
<div className="transcript-player">
|
||||
<button type="button" className="player-main-btn" onClick={toggleAudioPlayback} aria-label="toggle-audio">
|
||||
{audioPlaying ? <PauseOutlined /> : <CaretRightFilled />}
|
||||
</button>
|
||||
<div className="player-progress-shell">
|
||||
<div className="player-time-row">
|
||||
<span>{formatPlayerTime(audioCurrentTime)}</span>
|
||||
<span>{formatPlayerTime(audioDuration)}</span>
|
||||
</div>
|
||||
<input
|
||||
className="player-range"
|
||||
type="range"
|
||||
min={0}
|
||||
max={audioDuration || 0}
|
||||
step={0.1}
|
||||
value={Math.min(audioCurrentTime, audioDuration || 0)}
|
||||
onChange={handleAudioProgressChange}
|
||||
style={{ backgroundSize: `${audioDuration ? (audioCurrentTime / audioDuration) * 100 : 0}% 100%` }}
|
||||
/>
|
||||
</div>
|
||||
<button type="button" className="player-ghost-btn" onClick={cyclePlaybackRate}>
|
||||
<FastForwardOutlined />
|
||||
{audioPlaybackRate}x
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue