mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-06-04 07:36:48 +00:00
Merge pull request #933 from Jasonzhu1207/refactor/ui-rebuild
Refactor: UI Rebuild
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载和错误状态
|
||||
// Loading and error states
|
||||
.loading-container,
|
||||
.error-container {
|
||||
display: flex;
|
||||
@@ -23,7 +23,7 @@
|
||||
color: var(--text-secondary);
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
animation: analyticsSpin 1s linear infinite;
|
||||
}
|
||||
|
||||
p.loading-status {
|
||||
@@ -33,13 +33,12 @@
|
||||
}
|
||||
|
||||
.progress-bar-wrapper {
|
||||
width: 300px;
|
||||
height: 8px;
|
||||
width: 280px;
|
||||
height: 4px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
@@ -47,9 +46,9 @@
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background: var(--primary-gradient);
|
||||
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 0 10px rgba(139, 115, 85, 0.3);
|
||||
background: var(--primary);
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
@@ -65,57 +64,82 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@keyframes analyticsSpin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
// Page scroll content
|
||||
.page-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.page-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 统计卡片
|
||||
// Stats overview cards
|
||||
.stats-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
gap: 14px;
|
||||
padding: 18px 16px;
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--primary-light);
|
||||
border-radius: 12px;
|
||||
border-radius: 10px;
|
||||
color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 2px;
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
@@ -125,23 +149,23 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
|
||||
svg {
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Charts
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
@@ -155,30 +179,30 @@
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 16px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// Rankings
|
||||
.rankings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-tertiary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.rank {
|
||||
@@ -196,13 +220,13 @@
|
||||
|
||||
&.top {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
color: var(--on-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
@@ -228,8 +252,8 @@
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -239,24 +263,21 @@
|
||||
&.medal-1 {
|
||||
background: linear-gradient(135deg, #ffd700, #ffb800);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(255, 184, 0, 0.4);
|
||||
}
|
||||
|
||||
&.medal-2 {
|
||||
background: linear-gradient(135deg, #c0c0c0, #a8a8a8);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(168, 168, 168, 0.4);
|
||||
}
|
||||
|
||||
&.medal-3 {
|
||||
background: linear-gradient(135deg, #cd7f32, #b87333);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 4px rgba(184, 115, 51, 0.4);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +286,7 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
|
||||
.contact-name {
|
||||
@@ -284,14 +305,14 @@
|
||||
}
|
||||
|
||||
.message-count {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式
|
||||
// Responsive
|
||||
@media (max-width: 1200px) {
|
||||
.stats-overview {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@@ -312,11 +333,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 排除好友弹窗
|
||||
// Exclude friends modal
|
||||
.exclude-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -325,13 +346,13 @@
|
||||
}
|
||||
|
||||
.exclude-modal {
|
||||
width: 560px;
|
||||
width: 520px;
|
||||
max-width: calc(100vw - 48px);
|
||||
background: var(--card-bg);
|
||||
background: var(--bg-secondary-solid, var(--bg-secondary));
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.exclude-modal-header {
|
||||
display: flex;
|
||||
@@ -342,6 +363,7 @@
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
@@ -349,14 +371,14 @@
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: var(--bg-tertiary);
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-tertiary);
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
@@ -370,7 +392,7 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
margin-bottom: 12px;
|
||||
@@ -399,7 +421,7 @@
|
||||
}
|
||||
|
||||
.exclude-modal-body {
|
||||
max-height: 420px;
|
||||
max-height: 380px;
|
||||
overflow: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
@@ -419,7 +441,7 @@
|
||||
.exclude-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.exclude-item {
|
||||
@@ -427,23 +449,23 @@
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.15s;
|
||||
background: var(--bg-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-tertiary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: rgba(7, 193, 96, 0.4);
|
||||
background: rgba(7, 193, 96, 0.08);
|
||||
border-color: rgba(16, 163, 127, 0.3);
|
||||
background: rgba(16, 163, 127, 0.06);
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
accent-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +477,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
gap: 2px;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.exclude-name {
|
||||
@@ -479,7 +501,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.exclude-footer-left {
|
||||
|
||||
@@ -1,146 +1,116 @@
|
||||
.analytics-entry-page {
|
||||
.analytics-welcome-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.analytics-welcome-container {
|
||||
.analytics-welcome-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
padding: 40px;
|
||||
background: var(--bg-primary);
|
||||
padding: 40px 24px;
|
||||
animation: welcomeFadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
.analytics-welcome-content {
|
||||
text-align: center;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.analytics-welcome-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 20px;
|
||||
background: var(--primary-light);
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.analytics-welcome-content h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 10px;
|
||||
color: var(--text-primary);
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
overflow-y: auto;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
&.analytics-welcome-container--mode {
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(7, 193, 96, 0.06), transparent 48%),
|
||||
var(--bg-primary);
|
||||
.analytics-welcome-content p {
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 32px;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.analytics-welcome-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.analytics-welcome-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px 18px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
color: var(--text-secondary);
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--text-tertiary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
|
||||
.icon-wrapper {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto 24px;
|
||||
background: rgba(7, 193, 96, 0.1);
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #07c160;
|
||||
|
||||
svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 40px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.action-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 30px 20px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
border-color: #07c160;
|
||||
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.1);
|
||||
|
||||
.card-icon {
|
||||
color: #07c160;
|
||||
background: rgba(7, 193, 96, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 12px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.analytics-welcome-container {
|
||||
padding: 28px 18px;
|
||||
.analytics-welcome-card-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
.action-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.analytics-welcome-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.analytics-welcome-card-meta {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 540px) {
|
||||
.analytics-welcome-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@keyframes welcomeFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
||||
@@ -6,12 +6,6 @@ import './AnalyticsWelcomePage.scss'
|
||||
|
||||
function AnalyticsWelcomePage() {
|
||||
const navigate = useNavigate()
|
||||
// 检查是否有任何缓存数据加载或基本的存储状态表明它已准备好。
|
||||
// 实际上,如果 store 没有持久化,`isLoaded` 可能会在应用刷新时重置。
|
||||
// 如果用户点击“加载缓存”但缓存为空,AnalyticsPage 的逻辑(loadData 不带 force)将尝试从后端缓存加载。
|
||||
// 如果后端缓存也为空,则会重新计算。
|
||||
|
||||
// 我们也可以检查 `lastLoadTime` 来显示“上次更新:xxx”(如果已持久化)。
|
||||
const { lastLoadTime } = useAnalyticsStore()
|
||||
|
||||
const handleLoadCache = () => {
|
||||
@@ -28,35 +22,37 @@ function AnalyticsWelcomePage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="analytics-entry-page">
|
||||
<div className="analytics-welcome-shell">
|
||||
<ChatAnalysisHeader currentMode="private" />
|
||||
|
||||
<div className="analytics-welcome-container analytics-welcome-container--mode">
|
||||
<div className="welcome-content">
|
||||
<div className="icon-wrapper">
|
||||
<BarChart2 size={40} />
|
||||
<div className="analytics-welcome-body">
|
||||
<div className="analytics-welcome-content">
|
||||
<div className="analytics-welcome-icon">
|
||||
<BarChart2 size={32} />
|
||||
</div>
|
||||
<h1>私聊数据分析</h1>
|
||||
<p>
|
||||
WeFlow 可以分析你的好友聊天记录,生成详细的统计报表。<br />
|
||||
你可以选择加载上次的分析结果,或者重新开始一次新的私聊分析。
|
||||
分析你的好友聊天记录,生成详细统计报表。<br />
|
||||
选择加载上次结果或开始新分析。
|
||||
</p>
|
||||
|
||||
<div className="action-cards">
|
||||
<button onClick={handleLoadCache}>
|
||||
<div className="card-icon">
|
||||
<History size={24} />
|
||||
<div className="analytics-welcome-actions">
|
||||
<button className="analytics-welcome-card" onClick={handleLoadCache} type="button">
|
||||
<History size={20} />
|
||||
<div className="analytics-welcome-card-text">
|
||||
<span className="analytics-welcome-card-title">加载缓存</span>
|
||||
<span className="analytics-welcome-card-meta">
|
||||
上次更新: {formatLastTime(lastLoadTime)}
|
||||
</span>
|
||||
</div>
|
||||
<h3>加载缓存</h3>
|
||||
<span>查看上次分析结果<br />(上次更新: {formatLastTime(lastLoadTime)})</span>
|
||||
</button>
|
||||
|
||||
<button onClick={handleNewAnalysis}>
|
||||
<div className="card-icon">
|
||||
<RefreshCcw size={24} />
|
||||
<button className="analytics-welcome-card" onClick={handleNewAnalysis} type="button">
|
||||
<RefreshCcw size={20} />
|
||||
<div className="analytics-welcome-card-text">
|
||||
<span className="analytics-welcome-card-title">新的分析</span>
|
||||
<span className="analytics-welcome-card-meta">重新扫描并计算数据</span>
|
||||
</div>
|
||||
<h3>新的分析</h3>
|
||||
<span>重新扫描并计算数据<br />(可能需要几分钟)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
min-height: 100%;
|
||||
text-align: center;
|
||||
padding: 40px 24px;
|
||||
animation: reportFadeIn 0.35s ease-out;
|
||||
}
|
||||
|
||||
.annual-report-page.report-route-transitioning > :not(.report-launch-overlay) {
|
||||
@@ -20,40 +21,43 @@
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 12px;
|
||||
margin: 0 0 10px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 48px;
|
||||
margin: 0 0 40px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.page-desc.load-summary {
|
||||
margin: 0 0 28px;
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
|
||||
.page-desc.load-summary.complete {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
// ---- Load telemetry ----
|
||||
.load-telemetry {
|
||||
width: min(760px, 100%);
|
||||
padding: 12px 14px;
|
||||
margin: 0 0 28px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
|
||||
background: color-mix(in srgb, var(--card-bg) 92%, transparent);
|
||||
width: min(620px, 100%);
|
||||
padding: 12px 16px;
|
||||
margin: 0 0 24px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--card-bg);
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
|
||||
p {
|
||||
margin: 4px 0;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
@@ -62,31 +66,32 @@
|
||||
}
|
||||
|
||||
.load-telemetry.loading {
|
||||
border-color: color-mix(in srgb, var(--primary) 30%, var(--border-color));
|
||||
border-color: color-mix(in srgb, var(--primary) 25%, var(--border-color));
|
||||
}
|
||||
|
||||
.load-telemetry.complete {
|
||||
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
|
||||
border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color));
|
||||
}
|
||||
|
||||
.load-telemetry.compact {
|
||||
margin: 12px 0 0;
|
||||
width: min(560px, 100%);
|
||||
width: min(500px, 100%);
|
||||
}
|
||||
|
||||
// ---- Report sections ----
|
||||
.report-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
width: min(760px, 100%);
|
||||
gap: 20px;
|
||||
width: min(620px, 100%);
|
||||
}
|
||||
|
||||
.report-section {
|
||||
width: 100%;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding: 28px;
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -95,57 +100,57 @@
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
margin: 6px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.section-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.section-hint {
|
||||
margin: 12px 0 0;
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
// ---- Year cards ----
|
||||
.year-grid-with-status {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.year-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
margin-bottom: 48px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.report-section .year-grid {
|
||||
@@ -169,7 +174,7 @@
|
||||
}
|
||||
|
||||
.year-load-status.complete {
|
||||
color: color-mix(in srgb, var(--primary) 80%, var(--text-secondary));
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.dot-ellipsis {
|
||||
@@ -187,32 +192,33 @@
|
||||
}
|
||||
|
||||
.year-card {
|
||||
width: 120px;
|
||||
height: 100px;
|
||||
width: 88px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all 0.15s ease;
|
||||
gap: 2px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--text-tertiary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.72;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary-light);
|
||||
box-shadow: 0 0 0 1px var(--primary);
|
||||
|
||||
.year-number {
|
||||
color: var(--primary);
|
||||
@@ -220,45 +226,41 @@
|
||||
}
|
||||
|
||||
.year-number {
|
||||
font-size: 32px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.year-label {
|
||||
font-size: 14px;
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Generate button ----
|
||||
.generate-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 40px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, color-mix(in srgb, var(--primary) 80%, #000) 100%);
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
border-radius: 10px;
|
||||
color: var(--on-primary);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 4px 16px color-mix(in srgb, var(--primary) 30%, transparent);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, var(--primary) 40%, transparent);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -267,13 +269,18 @@
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: var(--card-bg);
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--bg-hover);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Launch overlay ----
|
||||
.report-launch-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -281,9 +288,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in srgb, var(--bg-primary) 78%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-primary) 80%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
animation: report-launch-overlay-in 420ms ease-out both;
|
||||
animation: report-launch-overlay-in 350ms ease-out both;
|
||||
}
|
||||
|
||||
.launch-core {
|
||||
@@ -293,13 +300,13 @@
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
animation: report-launch-core-in 420ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
animation: report-launch-core-in 350ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
}
|
||||
|
||||
.launch-title {
|
||||
margin: 4px 0 0;
|
||||
font-size: 18px;
|
||||
font-weight: 650;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.launch-subtitle {
|
||||
@@ -312,6 +319,7 @@
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
// ---- Animations ----
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
@@ -329,27 +337,34 @@
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
filter: blur(6px);
|
||||
transform: scale(0.985);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes report-launch-overlay-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes report-launch-core-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) scale(0.96);
|
||||
transform: translateY(14px) scale(0.97);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes reportFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.backup-page {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
@@ -10,13 +10,13 @@
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
font-size: 22px;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
.backup-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -38,17 +38,17 @@
|
||||
.resource-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin: -8px 0 18px;
|
||||
margin: -6px 0 14px;
|
||||
|
||||
label {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
min-height: 36px;
|
||||
padding: 8px 10px;
|
||||
min-height: 34px;
|
||||
padding: 7px 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -69,7 +69,7 @@
|
||||
.secondary-btn {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 9px 12px;
|
||||
padding: 8px 11px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -100,19 +100,19 @@
|
||||
}
|
||||
|
||||
.backup-status-band {
|
||||
min-height: 88px;
|
||||
min-height: 76px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
padding: 16px 0;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--primary);
|
||||
@@ -142,8 +142,8 @@
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
margin-top: 12px;
|
||||
height: 6px;
|
||||
margin-top: 10px;
|
||||
height: 5px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
@@ -160,7 +160,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.summary-item,
|
||||
@@ -168,8 +168,8 @@
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
padding: 14px;
|
||||
min-height: 74px;
|
||||
padding: 12px;
|
||||
min-height: 66px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
@@ -185,14 +185,14 @@
|
||||
|
||||
strong {
|
||||
color: var(--text-primary);
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.backup-detail {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 18px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.detail-heading {
|
||||
@@ -200,11 +200,11 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -217,7 +217,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
div {
|
||||
border: 1px solid var(--border-color);
|
||||
@@ -246,7 +246,7 @@
|
||||
.db-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.db-row {
|
||||
@@ -255,7 +255,7 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 9px 0;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
|
||||
span {
|
||||
|
||||
234
src/pages/Chat/ChatHeader.tsx
Normal file
234
src/pages/Chat/ChatHeader.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Aperture,
|
||||
BarChart3,
|
||||
Calendar,
|
||||
Download,
|
||||
Image as ImageIcon,
|
||||
Info,
|
||||
Loader2,
|
||||
Mic,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Users
|
||||
} from 'lucide-react'
|
||||
import { Avatar } from '../../components/Avatar'
|
||||
import type { ChatSession } from '../../types/models'
|
||||
import type { BatchVoiceTaskType } from '../../stores/batchTranscribeStore'
|
||||
|
||||
export interface ChatHeaderProps {
|
||||
session: ChatSession
|
||||
isGroupChat: boolean
|
||||
standaloneSessionWindow: boolean
|
||||
showGroupMembersPanel: boolean
|
||||
showJumpPopover: boolean
|
||||
showInSessionSearch: boolean
|
||||
showDetailPanel: boolean
|
||||
shouldHideStandaloneDetailButton: boolean
|
||||
isPrivateSnsSupported: boolean
|
||||
isExportActionBusy: boolean
|
||||
isCurrentSessionExporting: boolean
|
||||
isPreparingExportDialog: boolean
|
||||
isBatchTranscribing: boolean
|
||||
runningBatchVoiceTaskType?: BatchVoiceTaskType
|
||||
isBatchDecrypting: boolean
|
||||
isRefreshingMessages: boolean
|
||||
isLoadingMessages: boolean
|
||||
currentSessionId?: string | null
|
||||
jumpCalendarWrapRef: React.RefObject<HTMLDivElement | null>
|
||||
onGroupAnalytics: () => void
|
||||
onToggleGroupMembersPanel: () => void
|
||||
onExportCurrentSession: () => void
|
||||
onOpenSnsTimeline: () => void
|
||||
onBatchTranscribe: () => void
|
||||
onBatchDecrypt: () => void
|
||||
onToggleJumpPopover: () => void
|
||||
onToggleInSessionSearch: () => void
|
||||
onRefreshMessages: () => void
|
||||
onToggleDetailPanel: () => void
|
||||
}
|
||||
|
||||
function ChatHeader({
|
||||
session,
|
||||
isGroupChat,
|
||||
standaloneSessionWindow,
|
||||
showGroupMembersPanel,
|
||||
showJumpPopover,
|
||||
showInSessionSearch,
|
||||
showDetailPanel,
|
||||
shouldHideStandaloneDetailButton,
|
||||
isPrivateSnsSupported,
|
||||
isExportActionBusy,
|
||||
isCurrentSessionExporting,
|
||||
isPreparingExportDialog,
|
||||
isBatchTranscribing,
|
||||
runningBatchVoiceTaskType,
|
||||
isBatchDecrypting,
|
||||
isRefreshingMessages,
|
||||
isLoadingMessages,
|
||||
currentSessionId,
|
||||
jumpCalendarWrapRef,
|
||||
onGroupAnalytics,
|
||||
onToggleGroupMembersPanel,
|
||||
onExportCurrentSession,
|
||||
onOpenSnsTimeline,
|
||||
onBatchTranscribe,
|
||||
onBatchDecrypt,
|
||||
onToggleJumpPopover,
|
||||
onToggleInSessionSearch,
|
||||
onRefreshMessages,
|
||||
onToggleDetailPanel
|
||||
}: ChatHeaderProps) {
|
||||
const sessionName = session.displayName || session.username
|
||||
const exportTitle = isCurrentSessionExporting
|
||||
? '导出中'
|
||||
: isPreparingExportDialog
|
||||
? '正在准备导出模块'
|
||||
: '导出当前会话'
|
||||
const batchVoiceTitle = isBatchTranscribing
|
||||
? `${runningBatchVoiceTaskType === 'decrypt' ? '批量语音解密' : '批量转写'}中,可在导出页任务中心查看进度`
|
||||
: '批量语音处理'
|
||||
|
||||
return (
|
||||
<div className="message-header">
|
||||
<Avatar
|
||||
src={session.avatarUrl}
|
||||
name={sessionName}
|
||||
size={40}
|
||||
className={isGroupChat ? 'group session-avatar' : 'session-avatar'}
|
||||
/>
|
||||
<div className="header-info">
|
||||
<h3>{sessionName}</h3>
|
||||
{isGroupChat && <div className="header-subtitle">群聊</div>}
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
{!standaloneSessionWindow && isGroupChat && (
|
||||
<button className="icon-btn group-analytics-btn" onClick={onGroupAnalytics} title="群聊分析">
|
||||
<BarChart3 size={18} />
|
||||
</button>
|
||||
)}
|
||||
{isGroupChat && (
|
||||
<button
|
||||
className={`icon-btn group-members-btn ${showGroupMembersPanel ? 'active' : ''}`}
|
||||
onClick={onToggleGroupMembersPanel}
|
||||
title="群成员"
|
||||
>
|
||||
<Users size={18} />
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn export-session-btn${isExportActionBusy ? ' exporting' : ''}`}
|
||||
onClick={onExportCurrentSession}
|
||||
disabled={!currentSessionId || isExportActionBusy}
|
||||
title={exportTitle}
|
||||
>
|
||||
{isExportActionBusy ? <Loader2 size={18} className="spin" /> : <Download size={18} />}
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && isPrivateSnsSupported && (
|
||||
<button
|
||||
className="icon-btn chat-sns-timeline-btn"
|
||||
onClick={onOpenSnsTimeline}
|
||||
disabled={!currentSessionId}
|
||||
title="查看朋友圈"
|
||||
>
|
||||
<Aperture size={18} />
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn batch-transcribe-btn${isBatchTranscribing ? ' transcribing' : ''}`}
|
||||
onClick={onBatchTranscribe}
|
||||
disabled={!currentSessionId}
|
||||
title={batchVoiceTitle}
|
||||
>
|
||||
{isBatchTranscribing ? <Loader2 size={18} className="spin" /> : <Mic size={18} />}
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn batch-decrypt-btn${isBatchDecrypting ? ' transcribing' : ''}`}
|
||||
onClick={onBatchDecrypt}
|
||||
disabled={!currentSessionId}
|
||||
title={isBatchDecrypting ? '批量解密中' : '批量解密图片'}
|
||||
>
|
||||
{isBatchDecrypting ? <Loader2 size={18} className="spin" /> : <ImageIcon size={18} />}
|
||||
</button>
|
||||
)}
|
||||
<div className="jump-calendar-anchor" ref={jumpCalendarWrapRef}>
|
||||
<button
|
||||
className={`icon-btn jump-to-time-btn ${showJumpPopover ? 'active' : ''}`}
|
||||
onClick={onToggleJumpPopover}
|
||||
title="跳转到指定时间"
|
||||
>
|
||||
<Calendar size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className={`icon-btn in-session-search-btn ${showInSessionSearch ? 'active' : ''}`}
|
||||
onClick={onToggleInSessionSearch}
|
||||
disabled={!currentSessionId}
|
||||
title="搜索会话消息"
|
||||
>
|
||||
<Search size={18} />
|
||||
</button>
|
||||
<button
|
||||
className="icon-btn refresh-messages-btn"
|
||||
onClick={onRefreshMessages}
|
||||
disabled={isRefreshingMessages || isLoadingMessages}
|
||||
title="刷新消息"
|
||||
>
|
||||
<RefreshCw size={18} className={isRefreshingMessages ? 'spin' : ''} />
|
||||
</button>
|
||||
{!shouldHideStandaloneDetailButton && (
|
||||
<button
|
||||
className={`icon-btn detail-btn ${showDetailPanel ? 'active' : ''}`}
|
||||
onClick={onToggleDetailPanel}
|
||||
title="会话详情"
|
||||
>
|
||||
<Info size={18} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function areEqual(prev: ChatHeaderProps, next: ChatHeaderProps) {
|
||||
return (
|
||||
prev.session.username === next.session.username &&
|
||||
prev.session.displayName === next.session.displayName &&
|
||||
prev.session.avatarUrl === next.session.avatarUrl &&
|
||||
prev.isGroupChat === next.isGroupChat &&
|
||||
prev.standaloneSessionWindow === next.standaloneSessionWindow &&
|
||||
prev.showGroupMembersPanel === next.showGroupMembersPanel &&
|
||||
prev.showJumpPopover === next.showJumpPopover &&
|
||||
prev.showInSessionSearch === next.showInSessionSearch &&
|
||||
prev.showDetailPanel === next.showDetailPanel &&
|
||||
prev.shouldHideStandaloneDetailButton === next.shouldHideStandaloneDetailButton &&
|
||||
prev.isPrivateSnsSupported === next.isPrivateSnsSupported &&
|
||||
prev.isExportActionBusy === next.isExportActionBusy &&
|
||||
prev.isCurrentSessionExporting === next.isCurrentSessionExporting &&
|
||||
prev.isPreparingExportDialog === next.isPreparingExportDialog &&
|
||||
prev.isBatchTranscribing === next.isBatchTranscribing &&
|
||||
prev.runningBatchVoiceTaskType === next.runningBatchVoiceTaskType &&
|
||||
prev.isBatchDecrypting === next.isBatchDecrypting &&
|
||||
prev.isRefreshingMessages === next.isRefreshingMessages &&
|
||||
prev.isLoadingMessages === next.isLoadingMessages &&
|
||||
prev.currentSessionId === next.currentSessionId &&
|
||||
prev.jumpCalendarWrapRef === next.jumpCalendarWrapRef &&
|
||||
prev.onGroupAnalytics === next.onGroupAnalytics &&
|
||||
prev.onToggleGroupMembersPanel === next.onToggleGroupMembersPanel &&
|
||||
prev.onExportCurrentSession === next.onExportCurrentSession &&
|
||||
prev.onOpenSnsTimeline === next.onOpenSnsTimeline &&
|
||||
prev.onBatchTranscribe === next.onBatchTranscribe &&
|
||||
prev.onBatchDecrypt === next.onBatchDecrypt &&
|
||||
prev.onToggleJumpPopover === next.onToggleJumpPopover &&
|
||||
prev.onToggleInSessionSearch === next.onToggleInSessionSearch &&
|
||||
prev.onRefreshMessages === next.onRefreshMessages &&
|
||||
prev.onToggleDetailPanel === next.onToggleDetailPanel
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ChatHeader, areEqual)
|
||||
136
src/pages/Chat/ChatMessageBubble.tsx
Normal file
136
src/pages/Chat/ChatMessageBubble.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { Avatar } from '../../components/Avatar'
|
||||
import type { ChatSession, Message } from '../../types/models'
|
||||
|
||||
export interface ChatMessageBubbleProps {
|
||||
message: Message
|
||||
messageKey: string
|
||||
session: ChatSession
|
||||
showTime?: boolean
|
||||
timeText?: string
|
||||
isSent: boolean
|
||||
isSystem: boolean
|
||||
isEmoji?: boolean
|
||||
isImage?: boolean
|
||||
isVoice?: boolean
|
||||
emojiHasAsset?: boolean
|
||||
emojiError?: boolean
|
||||
avatarUrl?: string
|
||||
isGroupChat?: boolean
|
||||
resolvedSenderName?: string
|
||||
isSelectionMode?: boolean
|
||||
isSelected?: boolean
|
||||
onContextMenu?: (event: React.MouseEvent, message: Message) => void
|
||||
onToggleSelection?: (messageKey: string, isShiftKey?: boolean) => void
|
||||
children: React.ReactNode
|
||||
portal?: React.ReactNode
|
||||
}
|
||||
|
||||
function SelectionCheckbox({ checked, side }: { checked?: boolean; side: 'left' | 'right' }) {
|
||||
return (
|
||||
<div className={`chat-selection-checkbox ${side} ${checked ? 'checked' : ''}`}>
|
||||
{checked && <Check size={14} strokeWidth={3} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ChatMessageBubble({
|
||||
message,
|
||||
messageKey,
|
||||
session,
|
||||
showTime,
|
||||
timeText,
|
||||
isSent,
|
||||
isSystem,
|
||||
isEmoji,
|
||||
isImage,
|
||||
isVoice,
|
||||
emojiHasAsset,
|
||||
emojiError,
|
||||
avatarUrl,
|
||||
isGroupChat,
|
||||
resolvedSenderName,
|
||||
isSelectionMode,
|
||||
isSelected,
|
||||
onContextMenu,
|
||||
onToggleSelection,
|
||||
children,
|
||||
portal
|
||||
}: ChatMessageBubbleProps) {
|
||||
const bubbleClass = isSystem ? 'system' : (isSent ? 'sent' : 'received')
|
||||
const avatarName = !isSent
|
||||
? (isGroupChat ? (resolvedSenderName || '?') : (session.displayName || session.username))
|
||||
: '我'
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTime && timeText && (
|
||||
<div className="time-divider">
|
||||
<span>{timeText}</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`message-wrapper-with-selection ${isSelectionMode ? 'selectable' : ''}`}
|
||||
data-sent={isSent ? 'true' : 'false'}
|
||||
onClick={(event) => {
|
||||
if (!isSelectionMode) return
|
||||
event.stopPropagation()
|
||||
onToggleSelection?.(messageKey, event.shiftKey)
|
||||
}}
|
||||
>
|
||||
{isSelectionMode && !isSent && <SelectionCheckbox checked={isSelected} side="left" />}
|
||||
|
||||
<div
|
||||
className={`message-bubble ${bubbleClass} ${isEmoji && emojiHasAsset && !emojiError ? 'emoji' : ''} ${isImage ? 'image' : ''} ${isVoice ? 'voice' : ''}`}
|
||||
onContextMenu={(event) => onContextMenu?.(event, message)}
|
||||
>
|
||||
<div className="bubble-avatar">
|
||||
<Avatar src={avatarUrl} name={avatarName} size={36} className="bubble-avatar" />
|
||||
</div>
|
||||
<div className="bubble-body">
|
||||
{isGroupChat && !isSent && (
|
||||
<div className="sender-name">
|
||||
{resolvedSenderName || '群成员'}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isSelectionMode && isSent && <SelectionCheckbox checked={isSelected} side="right" />}
|
||||
{portal}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function areEqual(prev: ChatMessageBubbleProps, next: ChatMessageBubbleProps) {
|
||||
return (
|
||||
prev.message === next.message &&
|
||||
prev.messageKey === next.messageKey &&
|
||||
prev.session.username === next.session.username &&
|
||||
prev.session.displayName === next.session.displayName &&
|
||||
prev.session.avatarUrl === next.session.avatarUrl &&
|
||||
prev.showTime === next.showTime &&
|
||||
prev.timeText === next.timeText &&
|
||||
prev.isSent === next.isSent &&
|
||||
prev.isSystem === next.isSystem &&
|
||||
prev.isEmoji === next.isEmoji &&
|
||||
prev.isImage === next.isImage &&
|
||||
prev.isVoice === next.isVoice &&
|
||||
prev.emojiHasAsset === next.emojiHasAsset &&
|
||||
prev.emojiError === next.emojiError &&
|
||||
prev.avatarUrl === next.avatarUrl &&
|
||||
prev.isGroupChat === next.isGroupChat &&
|
||||
prev.resolvedSenderName === next.resolvedSenderName &&
|
||||
prev.isSelectionMode === next.isSelectionMode &&
|
||||
prev.isSelected === next.isSelected &&
|
||||
prev.onContextMenu === next.onContextMenu &&
|
||||
prev.onToggleSelection === next.onToggleSelection &&
|
||||
prev.children === next.children &&
|
||||
prev.portal === next.portal
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ChatMessageBubble, areEqual)
|
||||
@@ -1,4 +1,4 @@
|
||||
.chat-analytics-hub-page {
|
||||
.analytics-hub {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -6,118 +6,125 @@
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.chat-analytics-hub-content {
|
||||
width: min(860px, 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
.analytics-hub-inner {
|
||||
width: min(560px, 100%);
|
||||
animation: analyticsHubFadeIn 0.35s ease-out;
|
||||
}
|
||||
|
||||
.chat-analytics-hub-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
// ---- Hero ----
|
||||
.analytics-hub-hero {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.chat-analytics-hub-content h1 {
|
||||
margin: 20px 0 12px;
|
||||
font-size: 32px;
|
||||
line-height: 1.2;
|
||||
.analytics-hub-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 10px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.chat-analytics-hub-desc {
|
||||
max-width: 620px;
|
||||
margin: 0 0 32px;
|
||||
.analytics-hub-desc {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.chat-analytics-hub-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chat-analytics-entry-card {
|
||||
// ---- Perspectives list ----
|
||||
.analytics-hub-perspectives {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.analytics-hub-perspectives-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-tertiary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.analytics-hub-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 260px;
|
||||
padding: 28px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(7, 193, 96, 0.08) 0%, rgba(7, 193, 96, 0.02) 100%),
|
||||
var(--card-bg);
|
||||
padding: 14px 16px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
text-align: left;
|
||||
transition: background 0.15s ease;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(7, 193, 96, 0.35);
|
||||
box-shadow: 0 20px 36px rgba(7, 193, 96, 0.12);
|
||||
}
|
||||
background: var(--bg-hover);
|
||||
|
||||
.entry-card-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(7, 193, 96, 0.12);
|
||||
color: #07c160;
|
||||
|
||||
&.group {
|
||||
background: rgba(24, 119, 242, 0.12);
|
||||
color: #1877f2;
|
||||
.analytics-hub-row-arrow {
|
||||
transform: translateX(3px);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.entry-card-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.analytics-hub-row-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.entry-card-cta {
|
||||
margin-top: auto;
|
||||
color: var(--primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
&--group {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.chat-analytics-hub-grid {
|
||||
grid-template-columns: 1fr;
|
||||
[data-mode="dark"] .analytics-hub-row-icon--group {
|
||||
background: rgba(96, 165, 250, 0.12);
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.analytics-hub-row-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.analytics-hub-row-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.analytics-hub-row-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.analytics-hub-row-arrow {
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes analyticsHubFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowRight, BarChart3, MessageSquare, Users } from 'lucide-react'
|
||||
import { ArrowRight, MessageSquare, Users } from 'lucide-react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import './ChatAnalyticsHubPage.scss'
|
||||
|
||||
@@ -6,49 +6,46 @@ function ChatAnalyticsHubPage() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div className="chat-analytics-hub-page">
|
||||
<div className="chat-analytics-hub-content">
|
||||
<div className="chat-analytics-hub-badge">
|
||||
<BarChart3 size={16} />
|
||||
<span>聊天分析</span>
|
||||
<div className="analytics-hub">
|
||||
<div className="analytics-hub-inner">
|
||||
<div className="analytics-hub-hero">
|
||||
<h1 className="analytics-hub-title">聊天分析</h1>
|
||||
<p className="analytics-hub-desc">
|
||||
选择你要进入的分析视角,深入了解关系网络、活跃时段与消息趋势。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1>选择你要进入的分析视角</h1>
|
||||
<p className="chat-analytics-hub-desc">
|
||||
私聊分析更适合看好友聊天统计和趋势,群聊分析则用于查看群成员、发言排行和活跃时段。
|
||||
</p>
|
||||
<div className="analytics-hub-perspectives">
|
||||
<div className="analytics-hub-perspectives-label">视角</div>
|
||||
|
||||
<div className="chat-analytics-hub-grid">
|
||||
<button
|
||||
type="button"
|
||||
className="chat-analytics-entry-card"
|
||||
className="analytics-hub-row"
|
||||
onClick={() => navigate('/analytics/private')}
|
||||
>
|
||||
<div className="entry-card-icon">
|
||||
<MessageSquare size={24} />
|
||||
<div className="analytics-hub-row-icon">
|
||||
<MessageSquare size={18} />
|
||||
</div>
|
||||
<div className="entry-card-header">
|
||||
<h2>私聊分析</h2>
|
||||
<ArrowRight size={18} />
|
||||
<div className="analytics-hub-row-body">
|
||||
<div className="analytics-hub-row-title">私聊分析</div>
|
||||
<div className="analytics-hub-row-desc">查看好友聊天统计、消息趋势、活跃时段与联系人排名。</div>
|
||||
</div>
|
||||
<p>查看好友聊天统计、消息趋势、活跃时段与联系人排名。</p>
|
||||
<span className="entry-card-cta">进入私聊分析</span>
|
||||
<ArrowRight size={16} className="analytics-hub-row-arrow" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="chat-analytics-entry-card"
|
||||
className="analytics-hub-row"
|
||||
onClick={() => navigate('/analytics/group')}
|
||||
>
|
||||
<div className="entry-card-icon group">
|
||||
<Users size={24} />
|
||||
<div className="analytics-hub-row-icon analytics-hub-row-icon--group">
|
||||
<Users size={18} />
|
||||
</div>
|
||||
<div className="entry-card-header">
|
||||
<h2>群聊分析</h2>
|
||||
<ArrowRight size={18} />
|
||||
<div className="analytics-hub-row-body">
|
||||
<div className="analytics-hub-row-title">群聊分析</div>
|
||||
<div className="analytics-hub-row-desc">查看群成员信息、发言排行、活跃时段和媒体内容统计。</div>
|
||||
</div>
|
||||
<p>查看群成员信息、发言排行、活跃时段和媒体内容统计。</p>
|
||||
<span className="entry-card-cta">进入群聊分析</span>
|
||||
<ArrowRight size={16} className="analytics-hub-row-arrow" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--bg-primary) 96%, white) 0%, var(--bg-primary) 100%);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
|
||||
.history-list {
|
||||
flex: 1;
|
||||
@@ -149,7 +149,7 @@
|
||||
.nested-chat-record-card {
|
||||
min-width: 220px;
|
||||
max-width: 320px;
|
||||
background: color-mix(in srgb, var(--bg-secondary) 97%, #f5f7fb);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -2747,13 +2747,9 @@
|
||||
width: clamp(280px, 25vw, 360px);
|
||||
min-width: 280px;
|
||||
max-width: 360px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--card-bg) 94%, #fff 6%) 0%,
|
||||
var(--card-bg) 100%
|
||||
);
|
||||
background: var(--bg-primary);
|
||||
border-left: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
|
||||
box-shadow: -14px 0 28px rgba(0, 0, 0, 0.07);
|
||||
box-shadow: var(--shadow-sm);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -2766,7 +2762,7 @@
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 14px 14px 12px;
|
||||
background: color-mix(in srgb, var(--card-bg) 92%, #fff 8%);
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -2867,7 +2863,7 @@
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 84%, transparent);
|
||||
background: var(--bg-secondary);
|
||||
animation: detailCardEnter 0.24s ease both;
|
||||
|
||||
.detail-overview-avatar {
|
||||
@@ -2908,7 +2904,7 @@
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 72%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 86%, transparent);
|
||||
background: var(--bg-secondary);
|
||||
animation: detailCardEnter 0.24s ease both;
|
||||
|
||||
.section-title {
|
||||
@@ -2934,7 +2930,7 @@
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
background: color-mix(in srgb, var(--card-bg) 84%, transparent);
|
||||
background: var(--card-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5318,3 +5314,512 @@
|
||||
.in-session-search-btn.active {
|
||||
color: var(--accent-color, #07c160);
|
||||
}
|
||||
|
||||
// Modern chat surface overrides for the refactored Chat components.
|
||||
.chat-page {
|
||||
gap: 0;
|
||||
background: var(--bg-sidebar);
|
||||
|
||||
&:not(.standalone) {
|
||||
.message-area {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.session-sidebar {
|
||||
background: var(--bg-sidebar);
|
||||
border-right: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 18%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.message-area {
|
||||
background: var(--bg-primary);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-page.standalone {
|
||||
background: var(--bg-primary);
|
||||
|
||||
.session-sidebar {
|
||||
background: var(--bg-sidebar);
|
||||
border-right: 0;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.message-area {
|
||||
background: var(--bg-primary);
|
||||
|
||||
.message-header {
|
||||
min-height: 64px;
|
||||
padding: 12px 22px;
|
||||
border-bottom: 0;
|
||||
background: color-mix(in srgb, var(--bg-primary) 88%, transparent);
|
||||
backdrop-filter: blur(14px);
|
||||
box-shadow: 0 1px 0 color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
|
||||
.session-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.header-actions .icon-btn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-list {
|
||||
background: var(--bg-primary);
|
||||
padding: 18px clamp(16px, 3vw, 48px) 28px;
|
||||
padding-bottom: calc(28px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-header {
|
||||
min-height: 64px;
|
||||
padding: 12px 22px;
|
||||
border-bottom: 0;
|
||||
background: color-mix(in srgb, var(--bg-primary) 88%, transparent);
|
||||
backdrop-filter: blur(14px);
|
||||
box-shadow: 0 1px 0 color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
|
||||
.session-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
min-width: 0;
|
||||
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-content-wrapper {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.message-list {
|
||||
background: var(--bg-primary);
|
||||
padding: 18px clamp(16px, 3vw, 48px) 28px;
|
||||
padding-bottom: calc(28px + env(safe-area-inset-bottom));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.message-wrapper {
|
||||
padding-bottom: 14px;
|
||||
|
||||
&.sent {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&.received {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.message-wrapper-with-selection {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
cursor: default;
|
||||
|
||||
&[data-sent="true"] {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&[data-sent="false"] {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-selection-checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid color-mix(in srgb, var(--text-tertiary) 55%, transparent);
|
||||
background: transparent;
|
||||
color: var(--on-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 9px;
|
||||
|
||||
&.left {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
&.right {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
gap: 10px;
|
||||
max-width: min(76%, 760px);
|
||||
|
||||
.bubble-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.bubble-body {
|
||||
min-width: 0;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.bubble-content {
|
||||
border-radius: 18px;
|
||||
padding: 10px 14px;
|
||||
line-height: 1.56;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&.sent {
|
||||
.bubble-content {
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.bubble-body {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
&.received {
|
||||
.bubble-content {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border-radius: 18px;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.bubble-body {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&.system {
|
||||
max-width: min(86%, 680px);
|
||||
|
||||
.bubble-avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bubble-content {
|
||||
background: color-mix(in srgb, var(--bg-tertiary) 70%, transparent);
|
||||
color: var(--text-tertiary);
|
||||
border-radius: 999px;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.emoji,
|
||||
&.image {
|
||||
max-width: min(82%, 760px);
|
||||
|
||||
.bubble-content {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.voice.sent .bubble-content {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble .bubble-content:has(> .link-message),
|
||||
.message-bubble .bubble-content:has(> .card-message),
|
||||
.message-bubble .bubble-content:has(> .chat-record-message),
|
||||
.message-bubble .bubble-content:has(> .solitaire-message),
|
||||
.message-bubble .bubble-content:has(> .official-message),
|
||||
.message-bubble .bubble-content:has(> .channel-video-card),
|
||||
.message-bubble .bubble-content:has(> .location-message),
|
||||
.message-bubble .bubble-content:has(> .hongbao-message),
|
||||
.message-bubble .bubble-content:has(> .transfer-message),
|
||||
.message-bubble .bubble-content:has(> .gift-message),
|
||||
.message-bubble .bubble-content:has(> .miniapp-message),
|
||||
.message-bubble .bubble-content:has(> .file-message) {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.sender-name {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.quoted-message {
|
||||
background: transparent;
|
||||
border-left: 2px solid color-mix(in srgb, var(--primary) 62%, var(--text-tertiary));
|
||||
border-radius: 0;
|
||||
padding: 2px 0 2px 10px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.message-bubble.sent .quoted-message {
|
||||
background: color-mix(in srgb, var(--on-primary) 12%, transparent);
|
||||
border-left-color: color-mix(in srgb, var(--on-primary) 62%, var(--primary));
|
||||
|
||||
.quoted-sender,
|
||||
.quoted-text {
|
||||
color: color-mix(in srgb, var(--on-primary) 82%, var(--primary));
|
||||
}
|
||||
}
|
||||
|
||||
.link-message,
|
||||
.card-message,
|
||||
.chat-record-message,
|
||||
.solitaire-message,
|
||||
.official-message,
|
||||
.channel-video-card,
|
||||
.location-message,
|
||||
.miniapp-message,
|
||||
.file-message {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.link-message,
|
||||
.appmsg-rich-card {
|
||||
width: min(320px, calc(100vw - 112px));
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color));
|
||||
}
|
||||
|
||||
.link-thumb,
|
||||
.link-thumb-placeholder {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble.sent .link-message,
|
||||
.message-bubble.sent .card-message,
|
||||
.message-bubble.sent .miniapp-message,
|
||||
.message-bubble.sent .appmsg-rich-card {
|
||||
background: var(--card-bg);
|
||||
border-color: var(--border-color);
|
||||
|
||||
.card-name,
|
||||
.miniapp-title,
|
||||
.link-title {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.card-label,
|
||||
.miniapp-label,
|
||||
.link-desc,
|
||||
.appmsg-url-line {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.hongbao-message,
|
||||
.transfer-message,
|
||||
.gift-message {
|
||||
width: min(280px, calc(100vw - 112px));
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.hongbao-message,
|
||||
.transfer-message {
|
||||
padding: 14px;
|
||||
|
||||
.hongbao-icon,
|
||||
.transfer-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hongbao-icon {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.transfer-icon {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.hongbao-info,
|
||||
.transfer-info {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.hongbao-greeting,
|
||||
.transfer-amount {
|
||||
color: var(--text-primary);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.hongbao-label,
|
||||
.transfer-desc,
|
||||
.transfer-memo,
|
||||
.transfer-label {
|
||||
color: var(--text-secondary);
|
||||
opacity: 1;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-message.received .transfer-icon {
|
||||
background: #64748b;
|
||||
}
|
||||
|
||||
.gift-message {
|
||||
padding: 12px;
|
||||
|
||||
.gift-info {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.gift-wish,
|
||||
.gift-price {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.gift-label {
|
||||
color: var(--text-secondary);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[data-mode="dark"] {
|
||||
.hongbao-message,
|
||||
.transfer-message,
|
||||
.gift-message {
|
||||
background: var(--card-bg);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.message-bubble.received .bubble-content,
|
||||
.message-bubble.voice.sent .bubble-content {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-bottom {
|
||||
bottom: 20px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 92%, transparent);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: var(--shadow-sm);
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.message-header {
|
||||
padding-inline: 14px;
|
||||
|
||||
.header-actions {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.message-list {
|
||||
padding-inline: 14px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
max-width: 88%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
onSingleExportDialogStatus,
|
||||
requestExportSessionStatus
|
||||
} from '../services/exportBridge'
|
||||
import ChatHeader from './Chat/ChatHeader'
|
||||
import ChatMessageBubble from './Chat/ChatMessageBubble'
|
||||
import '../styles/batchTranscribe.scss'
|
||||
import './ChatPage.scss'
|
||||
|
||||
@@ -7027,155 +7029,62 @@ function ChatPage(props: ChatPageProps) {
|
||||
<BizMessageArea account={selectedBizAccount} />
|
||||
) : currentSession ? (
|
||||
<>
|
||||
<div className="message-header">
|
||||
<Avatar
|
||||
src={currentSession.avatarUrl}
|
||||
name={currentSession.displayName || currentSession.username}
|
||||
size={40}
|
||||
className={isCurrentSessionGroup ? 'group session-avatar' : 'session-avatar'}
|
||||
<ChatHeader
|
||||
session={currentSession}
|
||||
isGroupChat={isCurrentSessionGroup}
|
||||
standaloneSessionWindow={standaloneSessionWindow}
|
||||
showGroupMembersPanel={showGroupMembersPanel}
|
||||
showJumpPopover={showJumpPopover}
|
||||
showInSessionSearch={showInSessionSearch}
|
||||
showDetailPanel={showDetailPanel}
|
||||
shouldHideStandaloneDetailButton={shouldHideStandaloneDetailButton}
|
||||
isPrivateSnsSupported={isCurrentSessionPrivateSnsSupported}
|
||||
isExportActionBusy={isExportActionBusy}
|
||||
isCurrentSessionExporting={isCurrentSessionExporting}
|
||||
isPreparingExportDialog={isPreparingExportDialog}
|
||||
isBatchTranscribing={isBatchTranscribing}
|
||||
runningBatchVoiceTaskType={runningBatchVoiceTaskType}
|
||||
isBatchDecrypting={isBatchDecrypting}
|
||||
isRefreshingMessages={isRefreshingMessages}
|
||||
isLoadingMessages={isLoadingMessages}
|
||||
currentSessionId={currentSessionId}
|
||||
jumpCalendarWrapRef={jumpCalendarWrapRef}
|
||||
onGroupAnalytics={handleGroupAnalytics}
|
||||
onToggleGroupMembersPanel={toggleGroupMembersPanel}
|
||||
onExportCurrentSession={handleExportCurrentSession}
|
||||
onOpenSnsTimeline={openCurrentSessionSnsTimeline}
|
||||
onBatchTranscribe={handleBatchTranscribe}
|
||||
onBatchDecrypt={handleBatchDecrypt}
|
||||
onToggleJumpPopover={handleToggleJumpPopover}
|
||||
onToggleInSessionSearch={handleToggleInSessionSearch}
|
||||
onRefreshMessages={handleRefreshMessages}
|
||||
onToggleDetailPanel={toggleDetailPanel}
|
||||
/>
|
||||
<div className="header-info">
|
||||
<h3>{currentSession.displayName || currentSession.username}</h3>
|
||||
{isCurrentSessionGroup && (
|
||||
<div className="header-subtitle">群聊</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
{!standaloneSessionWindow && isCurrentSessionGroup && (
|
||||
<button
|
||||
className="icon-btn group-analytics-btn"
|
||||
onClick={handleGroupAnalytics}
|
||||
title="群聊分析"
|
||||
>
|
||||
<BarChart3 size={18} />
|
||||
</button>
|
||||
)}
|
||||
{isCurrentSessionGroup && (
|
||||
<button
|
||||
className={`icon-btn group-members-btn ${showGroupMembersPanel ? 'active' : ''}`}
|
||||
onClick={toggleGroupMembersPanel}
|
||||
title="群成员"
|
||||
>
|
||||
<Users size={18} />
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn export-session-btn${isExportActionBusy ? ' exporting' : ''}`}
|
||||
onClick={handleExportCurrentSession}
|
||||
disabled={!currentSessionId || isExportActionBusy}
|
||||
title={isCurrentSessionExporting ? '导出中' : isPreparingExportDialog ? '正在准备导出模块' : '导出当前会话'}
|
||||
>
|
||||
{isExportActionBusy ? (
|
||||
<Loader2 size={18} className="spin" />
|
||||
) : (
|
||||
<Download size={18} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && isCurrentSessionPrivateSnsSupported && (
|
||||
<button
|
||||
className="icon-btn chat-sns-timeline-btn"
|
||||
onClick={openCurrentSessionSnsTimeline}
|
||||
disabled={!currentSessionId}
|
||||
title="查看对方朋友圈"
|
||||
>
|
||||
<Aperture size={18} />
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn batch-transcribe-btn${isBatchTranscribing ? ' transcribing' : ''}`}
|
||||
onClick={handleBatchTranscribe}
|
||||
disabled={!currentSessionId}
|
||||
title={isBatchTranscribing
|
||||
? `${runningBatchVoiceTaskType === 'decrypt' ? '批量语音解密' : '批量转写'}中,可在导出页任务中心查看进度`
|
||||
: '批量语音处理(解密/转文字)'}
|
||||
>
|
||||
{isBatchTranscribing ? (
|
||||
<Loader2 size={18} className="spin" />
|
||||
) : (
|
||||
<Mic size={18} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{!standaloneSessionWindow && (
|
||||
<button
|
||||
className={`icon-btn batch-decrypt-btn${isBatchDecrypting ? ' transcribing' : ''}`}
|
||||
onClick={handleBatchDecrypt}
|
||||
disabled={!currentSessionId}
|
||||
title={isBatchDecrypting
|
||||
? '批量解密中,可在导出页任务中心查看进度'
|
||||
: '批量解密图片'}
|
||||
>
|
||||
{isBatchDecrypting ? (
|
||||
<Loader2 size={18} className="spin" />
|
||||
) : (
|
||||
<ImageIcon size={18} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<div className="jump-calendar-anchor" ref={jumpCalendarWrapRef}>
|
||||
<button
|
||||
className={`icon-btn jump-to-time-btn ${showJumpPopover ? 'active' : ''}`}
|
||||
onClick={handleToggleJumpPopover}
|
||||
title="跳转到指定时间"
|
||||
>
|
||||
<Calendar size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{showJumpPopover && createPortal(
|
||||
<div
|
||||
ref={jumpPopoverPortalRef}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: jumpPopoverPosition.top,
|
||||
left: jumpPopoverPosition.left,
|
||||
zIndex: 3600
|
||||
}}
|
||||
>
|
||||
<JumpToDatePopover
|
||||
isOpen={showJumpPopover}
|
||||
currentDate={jumpPopoverDate}
|
||||
onClose={() => setShowJumpPopover(false)}
|
||||
onSelect={handleJumpDateSelect}
|
||||
messageDates={messageDates}
|
||||
hasLoadedMessageDates={hasLoadedMessageDates}
|
||||
messageDateCounts={messageDateCounts}
|
||||
loadingDates={loadingDates}
|
||||
loadingDateCounts={loadingDateCounts}
|
||||
style={{ position: 'static', top: 'auto', right: 'auto' }}
|
||||
/>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
<button
|
||||
className={`icon-btn in-session-search-btn ${showInSessionSearch ? 'active' : ''}`}
|
||||
onClick={handleToggleInSessionSearch}
|
||||
disabled={!currentSessionId}
|
||||
title="搜索会话消息"
|
||||
{showJumpPopover && createPortal(
|
||||
<div
|
||||
ref={jumpPopoverPortalRef}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: jumpPopoverPosition.top,
|
||||
left: jumpPopoverPosition.left,
|
||||
zIndex: 3600
|
||||
}}
|
||||
>
|
||||
<Search size={18} />
|
||||
</button>
|
||||
<button
|
||||
className="icon-btn refresh-messages-btn"
|
||||
onClick={handleRefreshMessages}
|
||||
disabled={isRefreshingMessages || isLoadingMessages}
|
||||
title="刷新消息"
|
||||
>
|
||||
<RefreshCw size={18} className={isRefreshingMessages ? 'spin' : ''} />
|
||||
</button>
|
||||
{!shouldHideStandaloneDetailButton && (
|
||||
<button
|
||||
className={`icon-btn detail-btn ${showDetailPanel ? 'active' : ''}`}
|
||||
onClick={toggleDetailPanel}
|
||||
title="会话详情"
|
||||
>
|
||||
<Info size={18} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<JumpToDatePopover
|
||||
isOpen={showJumpPopover}
|
||||
currentDate={jumpPopoverDate}
|
||||
onClose={() => setShowJumpPopover(false)}
|
||||
onSelect={handleJumpDateSelect}
|
||||
messageDates={messageDates}
|
||||
hasLoadedMessageDates={hasLoadedMessageDates}
|
||||
messageDateCounts={messageDateCounts}
|
||||
loadingDates={loadingDates}
|
||||
loadingDateCounts={loadingDateCounts}
|
||||
style={{ position: 'static', top: 'auto', right: 'auto' }}
|
||||
/>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{isPreparingExportDialog && exportPrepareHint && (
|
||||
<div className="export-prepare-hint" role="status" aria-live="polite">
|
||||
@@ -10827,115 +10736,57 @@ function MessageBubble({
|
||||
return <div className="bubble-content">{renderTextWithEmoji(cleanedParsedContent)}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTime && (
|
||||
<div className="time-divider">
|
||||
<span>{formatTime(message.createTime)}</span>
|
||||
const systemAlertPortal = systemAlert ? createPortal(
|
||||
<div className="modal-overlay" onClick={() => setSystemAlert(null)} style={{ zIndex: 99999 }}>
|
||||
<div className="delete-confirm-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
|
||||
<div className="confirm-icon">
|
||||
<AlertCircle size={32} color="var(--danger)" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`message-wrapper-with-selection ${isSelectionMode ? 'selectable' : ''}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%',
|
||||
justifyContent: isSent ? 'flex-end' : 'flex-start',
|
||||
cursor: isSelectionMode ? 'pointer' : 'default'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (isSelectionMode) {
|
||||
e.stopPropagation()
|
||||
onToggleSelection?.(messageKey, e.shiftKey)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSelectionMode && !isSent && (
|
||||
<div className={`checkbox ${isSelected ? 'checked' : ''}`} style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '4px',
|
||||
border: isSelected ? 'none' : '2px solid rgba(128,128,128,0.5)',
|
||||
backgroundColor: isSelected ? 'var(--primary)' : 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
marginRight: '12px',
|
||||
marginTop: '10px', // Align with avatar top
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{isSelected && <Check size={14} strokeWidth={3} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`message-bubble ${bubbleClass} ${isEmoji && message.emojiCdnUrl && !emojiError ? 'emoji' : ''} ${isImage ? 'image' : ''} ${isVoice ? 'voice' : ''}`}
|
||||
onContextMenu={(e) => onContextMenu?.(e, message)}
|
||||
>
|
||||
<div className="bubble-avatar">
|
||||
<Avatar
|
||||
src={avatarUrl}
|
||||
name={!isSent ? (isGroupChat ? (resolvedSenderName || '?') : (session.displayName || session.username)) : '我'}
|
||||
size={36}
|
||||
className="bubble-avatar"
|
||||
/>
|
||||
</div>
|
||||
<div className="bubble-body">
|
||||
{/* 群聊中显示发送者名称 */}
|
||||
{isGroupChat && !isSent && (
|
||||
<div className="sender-name">
|
||||
{resolvedSenderName || '群成员'}
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
</div>
|
||||
<div className="confirm-content">
|
||||
<h3>{systemAlert.title}</h3>
|
||||
<p style={{ marginTop: '12px', lineHeight: '1.6', fontSize: '14px', color: 'var(--text-secondary)' }}>
|
||||
{systemAlert.message}
|
||||
</p>
|
||||
</div>
|
||||
<div className="confirm-actions" style={{ justifyContent: 'center', marginTop: '24px' }}>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => setSystemAlert(null)}
|
||||
style={{ padding: '8px 32px' }}
|
||||
>
|
||||
确认
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isSelectionMode && isSent && (
|
||||
<div className={`checkbox ${isSelected ? 'checked' : ''}`} style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '4px',
|
||||
border: isSelected ? 'none' : '2px solid rgba(128,128,128,0.5)',
|
||||
backgroundColor: isSelected ? 'var(--primary)' : 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
marginLeft: '12px',
|
||||
marginTop: '10px',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{isSelected && <Check size={14} strokeWidth={3} />}
|
||||
</div>
|
||||
)}
|
||||
{systemAlert && createPortal(
|
||||
<div className="modal-overlay" onClick={() => setSystemAlert(null)} style={{ zIndex: 99999 }}>
|
||||
<div className="delete-confirm-card" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
|
||||
<div className="confirm-icon">
|
||||
<AlertCircle size={32} color="var(--danger)" />
|
||||
</div>
|
||||
<div className="confirm-content">
|
||||
<h3>{systemAlert.title}</h3>
|
||||
<p style={{ marginTop: '12px', lineHeight: '1.6', fontSize: '14px', color: 'var(--text-secondary)' }}>
|
||||
{systemAlert.message}
|
||||
</p>
|
||||
</div>
|
||||
<div className="confirm-actions" style={{ justifyContent: 'center', marginTop: '24px' }}>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => setSystemAlert(null)}
|
||||
style={{ padding: '8px 32px' }}
|
||||
>
|
||||
确认
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</div>,
|
||||
document.body
|
||||
) : null
|
||||
|
||||
return (
|
||||
<ChatMessageBubble
|
||||
message={message}
|
||||
messageKey={messageKey}
|
||||
session={session}
|
||||
showTime={showTime}
|
||||
timeText={formatTime(message.createTime)}
|
||||
isSent={isSent}
|
||||
isSystem={isSystem}
|
||||
isEmoji={isEmoji}
|
||||
isImage={isImage}
|
||||
isVoice={isVoice}
|
||||
emojiHasAsset={Boolean(message.emojiCdnUrl || message.emojiLocalPath)}
|
||||
emojiError={emojiError}
|
||||
avatarUrl={avatarUrl}
|
||||
isGroupChat={isGroupChat}
|
||||
resolvedSenderName={resolvedSenderName}
|
||||
isSelectionMode={isSelectionMode}
|
||||
isSelected={isSelected}
|
||||
onContextMenu={onContextMenu}
|
||||
onToggleSelection={onToggleSelection}
|
||||
portal={systemAlertPortal}
|
||||
>
|
||||
{renderContent()}
|
||||
</ChatMessageBubble>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
// 左侧联系人面板
|
||||
.contacts-panel {
|
||||
width: 350px;
|
||||
min-width: 350px;
|
||||
width: 324px;
|
||||
min-width: 324px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--border-color);
|
||||
@@ -19,7 +19,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 24px;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
h2 {
|
||||
@@ -40,7 +40,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
@@ -67,10 +67,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 16px 20px;
|
||||
padding: 10px 14px;
|
||||
margin: 12px 16px;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: border-color 0.2s;
|
||||
|
||||
@@ -117,9 +117,9 @@
|
||||
.type-filters {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
padding: 0 20px 16px;
|
||||
max-width: 300px;
|
||||
gap: 6px;
|
||||
padding: 0 16px 12px;
|
||||
max-width: 292px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
@@ -129,16 +129,16 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
padding: 7px 10px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
input[type="checkbox"] {
|
||||
@@ -167,7 +167,7 @@
|
||||
color: var(--text-primary);
|
||||
|
||||
svg {
|
||||
transform: translateY(-1px);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
}
|
||||
|
||||
.contacts-count {
|
||||
padding: 0 20px 12px;
|
||||
padding: 0 16px 10px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 0 20px 12px;
|
||||
padding: 0 16px 10px;
|
||||
|
||||
.checkbox-item {
|
||||
font-size: 13px;
|
||||
@@ -249,7 +249,7 @@
|
||||
.issue-card {
|
||||
border: 1px solid color-mix(in srgb, var(--danger, #ef4444) 45%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--danger, #ef4444) 8%, var(--card-bg));
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
color: var(--text-primary);
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
@@ -335,7 +335,7 @@
|
||||
.contacts-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 12px 12px;
|
||||
padding: 0 10px 10px;
|
||||
position: relative;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@@ -358,7 +358,7 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 76px;
|
||||
height: 64px;
|
||||
padding-bottom: 4px;
|
||||
will-change: transform;
|
||||
}
|
||||
@@ -366,12 +366,12 @@
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
height: 72px;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s;
|
||||
border-radius: 8px;
|
||||
transition: background 0.15s ease;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -402,10 +402,10 @@
|
||||
}
|
||||
|
||||
.contact-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -419,8 +419,8 @@
|
||||
}
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -484,23 +484,23 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding-bottom: 24px;
|
||||
gap: 10px;
|
||||
padding-bottom: 18px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.detail-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
img { width: 100%; height: 100%; object-fit: cover; }
|
||||
span { color: #fff; font-size: 28px; font-weight: 600; }
|
||||
span { color: var(--text-secondary); font-size: 24px; font-weight: 600; }
|
||||
}
|
||||
|
||||
.detail-name {
|
||||
@@ -511,7 +511,7 @@
|
||||
}
|
||||
|
||||
.detail-info-list {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
@@ -565,15 +565,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover { background: var(--primary-hover); }
|
||||
}
|
||||
@@ -600,7 +600,7 @@
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px 24px;
|
||||
padding: 16px 18px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
@@ -613,7 +613,7 @@
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
margin-bottom: 28px;
|
||||
margin-bottom: 22px;
|
||||
|
||||
h3 {
|
||||
font-size: 13px;
|
||||
@@ -621,7 +621,7 @@
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 0 0 14px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,10 +631,9 @@
|
||||
|
||||
.select-trigger {
|
||||
width: 100%;
|
||||
padding: 10px 16px;
|
||||
padding: 9px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 9999px;
|
||||
/* Rounded pill shape */
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
@@ -643,7 +642,7 @@
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--text-tertiary);
|
||||
@@ -671,14 +670,12 @@
|
||||
right: 0;
|
||||
background: color-mix(in srgb, var(--bg-primary) 85%, var(--bg-secondary));
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
padding: 6px;
|
||||
box-shadow: var(--shadow-md);
|
||||
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
|
||||
z-index: 20;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.select-option {
|
||||
@@ -689,10 +686,10 @@
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
|
||||
@@ -740,9 +737,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
@@ -774,7 +771,7 @@
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-hover);
|
||||
@@ -786,10 +783,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.2s;
|
||||
@@ -797,7 +790,7 @@
|
||||
}
|
||||
|
||||
.export-action {
|
||||
padding: 20px 24px;
|
||||
padding: 16px 18px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@@ -807,15 +800,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 14px 24px;
|
||||
padding: 12px 16px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--primary-hover);
|
||||
@@ -841,3 +834,192 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// UI rebuild polish for the contact detail surface.
|
||||
.contacts-page {
|
||||
background: var(--bg-primary);
|
||||
|
||||
.contact-detail-panel {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.contact-detail-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 55%, transparent);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-detail-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
padding: 22px;
|
||||
border-radius: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 72%, transparent);
|
||||
}
|
||||
|
||||
.contact-detail-hero .detail-avatar {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 16px;
|
||||
background: var(--bg-tertiary);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--text-secondary);
|
||||
font-size: 28px;
|
||||
font-weight: 650;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-detail-heading {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
font-weight: 650;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-type {
|
||||
width: fit-content;
|
||||
border-radius: 999px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.contact-action-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.contact-action-row .goto-chat-btn,
|
||||
.contact-action-row .detail-entry-btn {
|
||||
min-height: 44px;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
padding: 10px 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.contact-action-row .goto-chat-btn {
|
||||
border: 1px solid var(--primary);
|
||||
background: var(--primary);
|
||||
color: var(--on-primary, #fff);
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-action-row .detail-entry-btn {
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 7%, var(--bg-secondary));
|
||||
}
|
||||
}
|
||||
|
||||
.contact-detail-section {
|
||||
padding: 18px;
|
||||
border-radius: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 72%, transparent);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.contact-detail-section .detail-info-list {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.contact-detail-section .detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: 76px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: var(--bg-primary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.contact-detail-section .detail-label {
|
||||
min-width: 0;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.contact-detail-section .detail-value {
|
||||
color: var(--text-primary);
|
||||
line-height: 1.55;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.contact-detail-hero {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ interface ContactEnrichInfo {
|
||||
|
||||
const AVATAR_ENRICH_BATCH_SIZE = 80
|
||||
const SEARCH_DEBOUNCE_MS = 120
|
||||
const VIRTUAL_ROW_HEIGHT = 76
|
||||
const VIRTUAL_ROW_HEIGHT = 64
|
||||
const VIRTUAL_OVERSCAN = 10
|
||||
const DEFAULT_CONTACTS_LOAD_TIMEOUT_MS = 10000
|
||||
const AVATAR_RECHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
|
||||
@@ -635,6 +635,39 @@ function ContactsPage() {
|
||||
return '朋友圈:统计中...'
|
||||
}, [selectedContactSupportsSns, selectedContactSnsCount, snsUserPostCountsStatus])
|
||||
|
||||
const selectedContactTitle = useMemo(() => {
|
||||
if (!selectedContact) return ''
|
||||
return selectedContact.displayName || selectedContact.remark || selectedContact.nickname || selectedContact.username
|
||||
}, [selectedContact])
|
||||
|
||||
const selectedContactSubtitle = useMemo(() => {
|
||||
if (!selectedContact) return ''
|
||||
const parts = [
|
||||
selectedContact.remark && selectedContact.remark !== selectedContactTitle ? `备注 ${selectedContact.remark}` : '',
|
||||
selectedContact.alias ? `微信号 ${selectedContact.alias}` : '',
|
||||
selectedContact.region || ''
|
||||
].filter(Boolean)
|
||||
return parts.join(' · ')
|
||||
}, [selectedContact, selectedContactTitle])
|
||||
|
||||
const selectedContactDetailRows = useMemo(() => {
|
||||
if (!selectedContact) return []
|
||||
return [
|
||||
{ key: 'username', label: '用户名', value: selectedContact.username },
|
||||
{ key: 'nickname', label: '昵称', value: selectedContact.nickname || selectedContact.displayName },
|
||||
selectedContact.remark ? { key: 'remark', label: '备注', value: selectedContact.remark } : null,
|
||||
selectedContact.alias ? { key: 'alias', label: '微信号', value: selectedContact.alias } : null,
|
||||
selectedContact.labels && selectedContact.labels.length > 0
|
||||
? { key: 'labels', label: '标签', value: selectedContact.labels.join('、') }
|
||||
: null,
|
||||
selectedContact.detailDescription
|
||||
? { key: 'signature', label: '个性签名', value: selectedContact.detailDescription }
|
||||
: null,
|
||||
selectedContact.region ? { key: 'region', label: '地区', value: selectedContact.region } : null,
|
||||
{ key: 'type', label: '类型', value: getContactTypeName(selectedContact.type) }
|
||||
].filter((row): row is { key: string; label: string; value: string } => Boolean(row && row.value))
|
||||
}, [selectedContact])
|
||||
|
||||
const openSelectedContactSnsTimeline = useCallback(() => {
|
||||
if (!selectedContact || !selectedContactSupportsSns) return
|
||||
if (snsUserPostCountsStatus === 'idle') {
|
||||
@@ -1090,12 +1123,9 @@ function ContactsPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : selectedContact ? (
|
||||
<div className="settings-panel">
|
||||
<div className="panel-header">
|
||||
<h2>联系人详情</h2>
|
||||
</div>
|
||||
<div className="settings-content">
|
||||
<div className="detail-profile">
|
||||
<div className="settings-panel contact-detail-panel">
|
||||
<div className="contact-detail-scroll">
|
||||
<section className="contact-detail-hero">
|
||||
<div className="detail-avatar">
|
||||
{selectedContact.avatarUrl ? (
|
||||
<img src={selectedContact.avatarUrl} alt="" />
|
||||
@@ -1103,53 +1133,50 @@ function ContactsPage() {
|
||||
<span>{getAvatarLetter(selectedContact.displayName)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="detail-name">{selectedContact.displayName}</div>
|
||||
<div className={`contact-type ${selectedContact.type}`}>
|
||||
{getContactTypeIcon(selectedContact.type)}
|
||||
<span>{getContactTypeName(selectedContact.type)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-info-list">
|
||||
<div className="detail-row"><span className="detail-label">用户名</span><span className="detail-value">{selectedContact.username}</span></div>
|
||||
<div className="detail-row"><span className="detail-label">昵称</span><span className="detail-value">{selectedContact.nickname || selectedContact.displayName}</span></div>
|
||||
{selectedContact.remark && <div className="detail-row"><span className="detail-label">备注</span><span className="detail-value">{selectedContact.remark}</span></div>}
|
||||
{selectedContact.alias && <div className="detail-row"><span className="detail-label">微信号</span><span className="detail-value">{selectedContact.alias}</span></div>}
|
||||
{selectedContact.labels && selectedContact.labels.length > 0 && (
|
||||
<div className="detail-row"><span className="detail-label">标签</span><span className="detail-value">{selectedContact.labels.join('、')}</span></div>
|
||||
)}
|
||||
{selectedContact.detailDescription && (
|
||||
<div className="detail-row"><span className="detail-label">个性签名</span><span className="detail-value">{selectedContact.detailDescription}</span></div>
|
||||
)}
|
||||
{selectedContact.region && (
|
||||
<div className="detail-row"><span className="detail-label">地区</span><span className="detail-value">{selectedContact.region}</span></div>
|
||||
)}
|
||||
<div className="detail-row"><span className="detail-label">类型</span><span className="detail-value">{getContactTypeName(selectedContact.type)}</span></div>
|
||||
{selectedContactSupportsSns && (
|
||||
<div className="detail-row">
|
||||
<span className="detail-label">朋友圈</span>
|
||||
<button
|
||||
type="button"
|
||||
className="detail-entry-btn"
|
||||
onClick={openSelectedContactSnsTimeline}
|
||||
>
|
||||
<Aperture size={14} />
|
||||
<span>{selectedContactSnsEntryLabel}</span>
|
||||
</button>
|
||||
<div className="contact-detail-heading">
|
||||
<div className={`contact-type detail-type ${selectedContact.type}`}>
|
||||
{getContactTypeIcon(selectedContact.type)}
|
||||
<span>{getContactTypeName(selectedContact.type)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h2>{selectedContactTitle}</h2>
|
||||
{selectedContactSubtitle && <p>{selectedContactSubtitle}</p>}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button
|
||||
className="goto-chat-btn"
|
||||
onClick={() => {
|
||||
setCurrentSession(selectedContact.username)
|
||||
navigate('/chat')
|
||||
}}
|
||||
>
|
||||
<MessageCircle size={18} />
|
||||
<span>查看聊天记录</span>
|
||||
</button>
|
||||
<section className="contact-action-row" aria-label="联系人操作">
|
||||
<button
|
||||
className="goto-chat-btn"
|
||||
onClick={() => {
|
||||
setCurrentSession(selectedContact.username)
|
||||
navigate('/chat')
|
||||
}}
|
||||
>
|
||||
<MessageCircle size={18} />
|
||||
<span>查看聊天记录</span>
|
||||
</button>
|
||||
{selectedContactSupportsSns && (
|
||||
<button
|
||||
type="button"
|
||||
className="detail-entry-btn"
|
||||
onClick={openSelectedContactSnsTimeline}
|
||||
>
|
||||
<Aperture size={18} />
|
||||
<span>{selectedContactSnsEntryLabel}</span>
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="contact-detail-section">
|
||||
<div className="section-title">基础资料</div>
|
||||
<div className="detail-info-list">
|
||||
{selectedContactDetailRows.map(row => (
|
||||
<div className="detail-row" key={row.key}>
|
||||
<span className="detail-label">{row.label}</span>
|
||||
<span className="detail-value">{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.dual-report-page {
|
||||
padding: 32px 28px;
|
||||
color: var(--text-primary);
|
||||
animation: dualFadeIn 0.35s ease-out;
|
||||
}
|
||||
|
||||
.dual-report-page.loading {
|
||||
@@ -22,25 +23,26 @@
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 8px 0 0;
|
||||
margin: 6px 0 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.year-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
@@ -50,11 +52,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
padding: 9px 12px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--text-tertiary);
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
@@ -69,7 +72,7 @@
|
||||
.ranking-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
@@ -77,42 +80,41 @@
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 10px 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-1px);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--border-color);
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
|
||||
&.top {
|
||||
background: color-mix(in srgb, var(--primary) 18%, transparent);
|
||||
color: var(--primary);
|
||||
background: var(--primary);
|
||||
color: var(--on-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: var(--primary-light);
|
||||
@@ -121,6 +123,7 @@
|
||||
justify-content: center;
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
@@ -132,11 +135,11 @@
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0; // 允许 flex 子项缩小,配合 ellipsis
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
|
||||
.name {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
@@ -146,29 +149,29 @@
|
||||
|
||||
.sub {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary); // 从 tertiary 改为 secondary 以增强对比度
|
||||
color: var(--text-tertiary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.meta {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary); // 改为 secondary
|
||||
color: var(--text-secondary);
|
||||
flex-shrink: 0;
|
||||
|
||||
.count {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--primary); // 使用主题色更醒目
|
||||
margin-bottom: 2px;
|
||||
color: var(--primary);
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
opacity: 0.7;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +179,7 @@
|
||||
text-align: center;
|
||||
color: var(--text-tertiary);
|
||||
padding: 40px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.spin {
|
||||
@@ -183,11 +187,17 @@
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes dualFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,14 @@
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
margin: -24px -24px 0;
|
||||
padding: 18px 22px 12px;
|
||||
padding: 16px 18px 12px;
|
||||
background: var(--bg-primary);
|
||||
/* Minimal background matching Footprint */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
animation: exportPageEnter 0.34s ease-out;
|
||||
|
||||
.spin {
|
||||
animation: exportSpin 1s linear infinite;
|
||||
@@ -22,7 +21,7 @@
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 55;
|
||||
animation: exportSectionReveal 0.34s ease both;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.export-top-bar {
|
||||
@@ -38,8 +37,8 @@
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 8px;
|
||||
animation: exportSectionReveal 0.38s ease both;
|
||||
margin-bottom: 6px;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.session-load-detail-entry {
|
||||
@@ -137,7 +136,7 @@
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.2px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.section-info-tooltip {
|
||||
@@ -209,10 +208,10 @@
|
||||
width: min(820px, 100%);
|
||||
max-height: min(78vh, 860px);
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 22px 46px rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: exportModalPopIn 0.24s cubic-bezier(0.2, 0.78, 0.26, 1) both;
|
||||
@@ -530,10 +529,10 @@
|
||||
width: min(760px, 100%);
|
||||
max-height: min(82vh, 900px);
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 22px 46px rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: exportModalPopIn 0.24s cubic-bezier(0.2, 0.78, 0.26, 1) both;
|
||||
@@ -559,7 +558,7 @@
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
background: var(--bg-tertiary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -754,29 +753,28 @@
|
||||
--top-inline-control-height: 34px;
|
||||
flex: 0 1 980px;
|
||||
width: min(980px, 100%);
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--card-bg) 75%, var(--bg-primary)) 0%, var(--card-bg) 100%);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 86%, transparent);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 14px 28px rgba(15, 23, 42, 0.08);
|
||||
padding: 13px;
|
||||
border-radius: 8px;
|
||||
box-shadow: none;
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(240px, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
animation: exportSectionReveal 0.4s ease both;
|
||||
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
|
||||
animation: none;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 30%, var(--border-color));
|
||||
box-shadow: 0 16px 30px rgba(15, 23, 42, 0.1);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.2px;
|
||||
letter-spacing: 0;
|
||||
width: 78px;
|
||||
flex: 0 0 78px;
|
||||
line-height: 1.2;
|
||||
@@ -879,7 +877,7 @@
|
||||
|
||||
.more-export-settings-btn {
|
||||
min-height: var(--top-inline-control-height);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
@@ -889,13 +887,12 @@
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease, transform 0.12s ease;
|
||||
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 6%, var(--bg-secondary));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,8 +931,8 @@
|
||||
max-width: calc(100vw - 40px);
|
||||
background: var(--bg-secondary-solid, #ffffff);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 88%, transparent);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 22px 38px rgba(15, 23, 42, 0.2);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.16);
|
||||
padding: 6px;
|
||||
z-index: 3000;
|
||||
max-height: 260px;
|
||||
@@ -1070,14 +1067,13 @@
|
||||
|
||||
.task-center-card {
|
||||
min-width: 92px;
|
||||
min-height: 42px;
|
||||
min-height: 38px;
|
||||
margin-left: auto;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 86%, transparent);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--card-bg) 80%, var(--bg-primary)) 0%, var(--card-bg) 100%);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 10px 12px;
|
||||
padding: 8px 11px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1087,15 +1083,14 @@
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
align-self: stretch;
|
||||
transition: border-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease, background 0.14s ease;
|
||||
animation: exportSectionReveal 0.46s ease both;
|
||||
transition: border-color 0.14s ease, color 0.14s ease, background 0.14s ease;
|
||||
animation: none;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 48%, var(--border-color));
|
||||
color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
background: color-mix(in srgb, var(--primary) 7%, var(--card-bg));
|
||||
box-shadow: 0 10px 18px rgba(15, 23, 42, 0.12);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.has-alert {
|
||||
@@ -1120,10 +1115,10 @@
|
||||
width: min(720px, 100%);
|
||||
max-height: min(80vh, 860px);
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 22px 46px rgba(0, 0, 0, 0.28);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: exportModalPopIn 0.24s cubic-bezier(0.2, 0.78, 0.26, 1) both;
|
||||
@@ -1162,6 +1157,30 @@
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.export-defaults-modal {
|
||||
width: min(740px, calc(100vw - 40px));
|
||||
max-height: min(82vh, 820px);
|
||||
border-radius: 16px;
|
||||
background: var(--bg-primary);
|
||||
border-color: color-mix(in srgb, var(--border-color) 68%, transparent);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.export-defaults-modal-header {
|
||||
padding: 16px 18px 10px;
|
||||
border-bottom-color: color-mix(in srgb, var(--border-color) 64%, transparent);
|
||||
}
|
||||
|
||||
.export-defaults-modal-body {
|
||||
padding: 12px 18px 14px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.export-defaults-modal-actions {
|
||||
padding: 0 18px 16px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.task-center-card-label {
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
@@ -1180,35 +1199,32 @@
|
||||
justify-content: center;
|
||||
padding: 0 5px;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.16);
|
||||
animation: exportTaskBadgePulse 1.2s ease-in-out infinite;
|
||||
box-shadow: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.content-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(142px, 1fr));
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
animation: exportSectionReveal 0.52s ease both;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 86%, transparent);
|
||||
border-radius: 13px;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--card-bg) 82%, var(--bg-primary)) 0%, var(--card-bg) 100%);
|
||||
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.06);
|
||||
padding: 11px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: none;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease, background 0.16s ease;
|
||||
animation: exportCardReveal 0.42s ease both;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
animation: none;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 38%, var(--border-color));
|
||||
box-shadow: 0 16px 24px rgba(15, 23, 42, 0.11);
|
||||
transform: translateY(-2px);
|
||||
background: color-mix(in srgb, var(--primary) 4%, var(--card-bg));
|
||||
}
|
||||
|
||||
@@ -1263,8 +1279,8 @@
|
||||
.card-export-btn {
|
||||
margin-top: auto;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
padding: 7px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
@@ -1281,8 +1297,7 @@
|
||||
|
||||
&.primary:hover {
|
||||
background: var(--primary-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 14px color-mix(in srgb, var(--primary) 24%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
@@ -1295,7 +1310,6 @@
|
||||
border-color: color-mix(in srgb, var(--primary) 28%, transparent);
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--bg-primary) 94%, var(--primary) 6%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -1372,10 +1386,10 @@
|
||||
.task-center-modal {
|
||||
width: min(980px, calc(100vw - 40px));
|
||||
max-height: calc(100vh - 72px);
|
||||
border-radius: 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary-solid, #ffffff);
|
||||
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -1677,16 +1691,15 @@
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 84%, transparent);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--card-bg) 72%, var(--bg-primary)) 0%, color-mix(in srgb, var(--card-bg) 90%, var(--bg-primary)) 100%);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.07);
|
||||
animation: exportSectionReveal 0.58s ease both;
|
||||
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: none;
|
||||
animation: none;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 20%, var(--border-color));
|
||||
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.09);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,24 +1709,24 @@
|
||||
gap: 6px;
|
||||
margin: 8px 12px 0;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
border-radius: 8px;
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
||||
color: var(--primary);
|
||||
font-size: 12px;
|
||||
width: fit-content;
|
||||
animation: exportSectionReveal 0.35s ease both;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 14px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--bg-primary) 88%, var(--card-bg)) 0%, color-mix(in srgb, var(--bg-primary) 92%, var(--bg-secondary)) 100%);
|
||||
background: var(--bg-secondary);
|
||||
transition: border-color 0.16s ease, background 0.16s ease;
|
||||
}
|
||||
|
||||
@@ -1751,7 +1764,7 @@
|
||||
color: var(--text-secondary);
|
||||
min-height: 32px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
@@ -1759,7 +1772,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
.tab-btn-content {
|
||||
display: inline-flex;
|
||||
@@ -1791,7 +1804,7 @@
|
||||
&.active {
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
|
||||
box-shadow: none;
|
||||
|
||||
.tab-btn-content span:last-child {
|
||||
background: color-mix(in srgb, var(--primary) 16%, transparent);
|
||||
@@ -1822,11 +1835,10 @@
|
||||
|
||||
.secondary-btn {
|
||||
min-height: 34px;
|
||||
border-radius: 10px;
|
||||
transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease, transform 0.14s ease;
|
||||
border-radius: 8px;
|
||||
transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
border-color: color-mix(in srgb, var(--primary) 36%, var(--border-color));
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 8%, var(--bg-secondary));
|
||||
@@ -1842,14 +1854,14 @@
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: color-mix(in srgb, var(--text-tertiary) 4%, transparent);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03) inset;
|
||||
box-shadow: none;
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
max-width: 320px;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent), 0 1px 3px rgba(0, 0, 0, 0.02) inset;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 24%, transparent);
|
||||
background: color-mix(in srgb, var(--text-tertiary) 6%, transparent);
|
||||
}
|
||||
|
||||
@@ -1874,7 +1886,7 @@
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
@@ -1909,31 +1921,31 @@
|
||||
|
||||
.table-wrap {
|
||||
--contacts-native-scrollbar-compensation: 18px;
|
||||
--contacts-row-height: 76px;
|
||||
--contacts-default-visible-rows: 8;
|
||||
--contacts-row-height: 64px;
|
||||
--contacts-default-visible-rows: 9;
|
||||
--contacts-default-list-height: calc(var(--contacts-row-height) * var(--contacts-default-visible-rows));
|
||||
--contacts-select-col-width: 34px;
|
||||
--contacts-avatar-col-width: 44px;
|
||||
--contacts-inline-padding: 12px;
|
||||
--contacts-column-gap: 10px;
|
||||
--contacts-select-col-width: 30px;
|
||||
--contacts-avatar-col-width: 38px;
|
||||
--contacts-inline-padding: 10px;
|
||||
--contacts-column-gap: 8px;
|
||||
--contacts-name-text-width: 9.5em;
|
||||
--contacts-main-col-width: calc(var(--contacts-avatar-col-width) + var(--contacts-column-gap) + var(--contacts-name-text-width));
|
||||
--contacts-left-sticky-width: calc(var(--contacts-select-col-width) + var(--contacts-main-col-width) + var(--contacts-column-gap));
|
||||
--contacts-message-col-width: 104px;
|
||||
--contacts-media-col-width: 66px;
|
||||
--contacts-action-col-width: 140px;
|
||||
--contacts-actions-sticky-width: 180px;
|
||||
--contacts-table-min-width: 1240px;
|
||||
--contacts-message-col-width: 94px;
|
||||
--contacts-media-col-width: 58px;
|
||||
--contacts-action-col-width: 126px;
|
||||
--contacts-actions-sticky-width: 160px;
|
||||
--contacts-table-min-width: 1120px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
min-height: 320px;
|
||||
height: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-secondary);
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 2%, var(--bg-secondary));
|
||||
@@ -2012,7 +2024,7 @@
|
||||
.issue-card {
|
||||
border: 1px solid color-mix(in srgb, var(--danger, #ef4444) 45%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--danger, #ef4444) 8%, var(--bg-secondary));
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@@ -2068,12 +2080,11 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--text-tertiary) 16%, transparent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.primary {
|
||||
@@ -2104,7 +2115,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--contacts-column-gap);
|
||||
padding: 10px var(--contacts-inline-padding) 8px;
|
||||
padding: 8px var(--contacts-inline-padding) 6px;
|
||||
min-width: max(100%, var(--contacts-table-min-width));
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--text-tertiary) 6%, transparent);
|
||||
background: var(--contacts-header-bg);
|
||||
@@ -2113,7 +2124,6 @@
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
flex-shrink: 0;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
&.is-draggable {
|
||||
cursor: grab;
|
||||
@@ -2273,12 +2283,11 @@
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--text-tertiary) 16%, transparent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -2301,13 +2310,12 @@
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 6px color-mix(in srgb, var(--primary) 20%, transparent);
|
||||
transition: background 0.15s ease;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--primary) 85%, #fff);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 14px color-mix(in srgb, var(--primary) 30%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.selection-export-count {
|
||||
@@ -2382,19 +2390,18 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--contacts-column-gap);
|
||||
padding: 12px var(--contacts-inline-padding);
|
||||
padding: 8px var(--contacts-inline-padding);
|
||||
min-width: max(100%, var(--contacts-table-min-width));
|
||||
height: 72px;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 8px;
|
||||
transition: background 0.15s ease;
|
||||
cursor: default;
|
||||
background: var(--contacts-row-bg);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 6%, transparent);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2421,10 +2428,10 @@
|
||||
}
|
||||
|
||||
.contact-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
width: var(--contacts-avatar-col-width);
|
||||
height: var(--contacts-avatar-col-width);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -2438,8 +2445,8 @@
|
||||
}
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -2740,7 +2747,7 @@
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -2752,7 +2759,7 @@
|
||||
}
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -2811,12 +2818,11 @@
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
background: color-mix(in srgb, var(--text-tertiary) 14%, transparent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -2923,7 +2929,7 @@
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 24px);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 40%, transparent);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2949,8 +2955,8 @@
|
||||
.detail-header-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -3248,13 +3254,12 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease, transform 0.14s ease;
|
||||
transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: color-mix(in srgb, var(--primary) 38%, var(--border-color));
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 9%, var(--bg-secondary));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -3321,10 +3326,10 @@
|
||||
.export-session-sns-dialog {
|
||||
width: min(760px, 100%);
|
||||
max-height: min(86vh, 860px);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--text-tertiary) 8%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -3349,8 +3354,8 @@
|
||||
.sns-dialog-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--primary), var(--primary-hover));
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
@@ -3739,7 +3744,7 @@
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(15, 18, 28, 0.48);
|
||||
backdrop-filter: blur(7px);
|
||||
backdrop-filter: blur(2px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -3752,9 +3757,9 @@
|
||||
max-height: calc(100vh - 40px);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 84%, transparent);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 26px 52px rgba(0, 0, 0, 0.3);
|
||||
padding: 16px 16px 14px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.22);
|
||||
padding: 14px 14px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -3789,9 +3794,9 @@
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 22px;
|
||||
font-size: 20px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.2px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3811,7 +3816,7 @@
|
||||
.close-icon-btn {
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 84%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 86%, var(--bg-primary));
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
@@ -3819,20 +3824,19 @@
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: border-color 0.16s ease, color 0.16s ease, transform 0.16s ease, background 0.16s ease;
|
||||
transition: border-color 0.16s ease, color 0.16s ease, background 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 42%, var(--border-color));
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 7%, var(--bg-primary));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-section {
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
|
||||
border-radius: 12px;
|
||||
padding: 13px 14px;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: color-mix(in srgb, var(--bg-secondary) 78%, var(--bg-primary));
|
||||
|
||||
h4 {
|
||||
@@ -3840,7 +3844,7 @@
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
@@ -3924,7 +3928,7 @@
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
|
||||
@@ -4007,8 +4011,8 @@
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 88%, transparent);
|
||||
border-radius: 11px;
|
||||
padding: 10px 11px;
|
||||
border-radius: 8px;
|
||||
padding: 9px 10px;
|
||||
text-align: left;
|
||||
background: color-mix(in srgb, var(--bg-primary) 70%, var(--bg-secondary));
|
||||
cursor: pointer;
|
||||
@@ -4016,7 +4020,7 @@
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
.format-label {
|
||||
font-size: 14px;
|
||||
@@ -4034,7 +4038,6 @@
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 36%, var(--border-color));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -4102,20 +4105,19 @@
|
||||
.media-option-card {
|
||||
position: relative;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 86%, transparent);
|
||||
border-radius: 12px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 72%, var(--bg-secondary));
|
||||
min-height: 74px;
|
||||
padding: 10px 11px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 76%, var(--bg-secondary));
|
||||
min-height: 68px;
|
||||
padding: 9px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 9px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 46%, var(--border-color));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:has(.media-option-input:focus-visible) {
|
||||
@@ -4126,7 +4128,7 @@
|
||||
&.active {
|
||||
border-color: color-mix(in srgb, var(--primary) 76%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 12%, var(--bg-secondary));
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 28%, transparent);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 26%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4147,7 +4149,7 @@
|
||||
.media-option-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 9px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-primary) 86%, var(--bg-secondary));
|
||||
color: var(--text-secondary);
|
||||
@@ -4428,7 +4430,7 @@
|
||||
font: inherit;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid rgba(var(--primary-rgb), 0.35);
|
||||
@@ -4437,7 +4439,6 @@
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 44%, var(--border-color));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -4467,14 +4468,12 @@
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(180deg,
|
||||
transparent,
|
||||
var(--card-bg) 38%);
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
.primary-btn,
|
||||
.secondary-btn {
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
min-height: 38px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
@@ -4484,7 +4483,7 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease, transform 0.15s ease;
|
||||
transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
@@ -4495,7 +4494,6 @@
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -4513,7 +4511,6 @@
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 7%, var(--bg-secondary));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5028,10 +5025,10 @@
|
||||
.table-wrap {
|
||||
--contacts-inline-padding: 10px;
|
||||
--contacts-name-text-width: 10em;
|
||||
--contacts-main-col-width: calc(44px + 10px + var(--contacts-name-text-width));
|
||||
--contacts-message-col-width: 104px;
|
||||
--contacts-media-col-width: 62px;
|
||||
--contacts-action-col-width: 140px;
|
||||
--contacts-main-col-width: calc(var(--contacts-avatar-col-width) + var(--contacts-column-gap) + var(--contacts-name-text-width));
|
||||
--contacts-message-col-width: 94px;
|
||||
--contacts-media-col-width: 56px;
|
||||
--contacts-action-col-width: 126px;
|
||||
}
|
||||
|
||||
.table-wrap .contacts-list-header {
|
||||
@@ -5094,7 +5091,7 @@
|
||||
width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 20px);
|
||||
padding: 12px 10px 10px;
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
@@ -5266,10 +5263,10 @@
|
||||
.automation-modal {
|
||||
width: min(680px, 100%);
|
||||
max-height: min(80vh, 820px);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--text-tertiary) 8%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -5320,9 +5317,9 @@
|
||||
}
|
||||
|
||||
.automation-task-card {
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--text-tertiary) 5%, transparent);
|
||||
padding: 14px 16px;
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
@@ -5411,10 +5408,10 @@
|
||||
.automation-editor-modal {
|
||||
width: min(560px, 100%);
|
||||
max-height: min(88vh, 900px);
|
||||
border-radius: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--text-tertiary) 8%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -5683,7 +5680,7 @@
|
||||
border-radius: 4px;
|
||||
border: 1px solid color-mix(in srgb, var(--text-tertiary) 30%, transparent);
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
@@ -5724,7 +5721,7 @@
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 14%, transparent);
|
||||
@@ -5749,7 +5746,7 @@
|
||||
|
||||
.automation-draft-summary {
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--text-tertiary) 5%, transparent);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
@@ -5768,7 +5765,7 @@
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.18s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 16%, transparent);
|
||||
@@ -5778,20 +5775,19 @@
|
||||
|
||||
.primary-btn {
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
border-radius: 8px;
|
||||
padding: 8px 18px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 6px color-mix(in srgb, var(--primary) 20%, transparent);
|
||||
transition: background 0.15s ease;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--primary) 85%, #fff);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 14px color-mix(in srgb, var(--primary) 30%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -5819,7 +5815,7 @@
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: background 0.15s ease, box-shadow 0.15s ease;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -9552,7 +9552,7 @@ function ExportPage() {
|
||||
customScrollParent={contactsListScrollParent ?? undefined}
|
||||
data={filteredContacts}
|
||||
computeItemKey={(_, contact) => contact.username}
|
||||
fixedItemHeight={76}
|
||||
fixedItemHeight={64}
|
||||
itemContent={renderContactRow}
|
||||
rangeChanged={handleContactsRangeChanged}
|
||||
atTopStateChange={setIsContactsListAtTop}
|
||||
|
||||
@@ -1955,3 +1955,276 @@
|
||||
opacity: 0.92;
|
||||
}
|
||||
}
|
||||
|
||||
// UI rebuild polish: align group analysis with the shared ChatGPT-style tokens.
|
||||
.group-analytics-shell {
|
||||
gap: 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.group-analytics-page {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.group-sidebar,
|
||||
.detail-area {
|
||||
border-radius: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 72%, transparent);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.group-sidebar {
|
||||
.sidebar-header {
|
||||
min-height: 58px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
border-radius: 12px;
|
||||
border: 1px solid transparent;
|
||||
background: var(--bg-primary);
|
||||
|
||||
&:focus-within {
|
||||
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.group-item {
|
||||
margin: 0 8px 4px;
|
||||
border-bottom: none;
|
||||
border-radius: 12px;
|
||||
|
||||
&.active {
|
||||
background: color-mix(in srgb, var(--primary) 12%, var(--bg-secondary));
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.group-avatar {
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-drag-region {
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.function-menu {
|
||||
padding: 18px 22px 24px;
|
||||
|
||||
.selected-group-info {
|
||||
border-radius: 16px;
|
||||
border-color: color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.function-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.function-card {
|
||||
min-height: 136px;
|
||||
border-radius: 16px;
|
||||
border-color: color-mix(in srgb, var(--border-color) 76%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
border-color: color-mix(in srgb, var(--primary) 35%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 5%, var(--bg-primary));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.function-content {
|
||||
.content-header {
|
||||
padding: 16px 22px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom-color: color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
|
||||
.back-btn,
|
||||
.refresh-btn,
|
||||
.export-btn {
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
padding: 18px 22px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.members-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
|
||||
gap: 10px;
|
||||
|
||||
.member-card {
|
||||
border-radius: 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--primary) 5%, var(--bg-primary));
|
||||
border-color: color-mix(in srgb, var(--primary) 28%, var(--border-color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-export-panel,
|
||||
.member-messages-panel,
|
||||
.member-analytics-panel {
|
||||
.select-trigger {
|
||||
border-radius: 12px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.member-export-options,
|
||||
.member-message-item,
|
||||
.member-message-summary-card {
|
||||
border-radius: 12px;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rankings-list {
|
||||
.ranking-item {
|
||||
border-radius: 12px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--border-color);
|
||||
background: color-mix(in srgb, var(--primary) 4%, var(--bg-primary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-stats {
|
||||
.media-layout .media-legend,
|
||||
.stat-card,
|
||||
.charts-grid .chart-card {
|
||||
border-radius: 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 76%, transparent);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.member-modal-overlay {
|
||||
background: rgba(0, 0, 0, 0.42);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.member-modal,
|
||||
.member-export-modal,
|
||||
.member-result-modal {
|
||||
border-radius: 16px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-primary) 96%, transparent);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.member-modal {
|
||||
width: min(380px, calc(100vw - 32px));
|
||||
padding: 28px 32px 32px;
|
||||
|
||||
.member-display-name {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
border-radius: 12px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.member-modal-actions {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.member-modal-primary-btn,
|
||||
.member-modal-secondary-btn {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
span {
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.member-modal-primary-btn {
|
||||
border: 1px solid var(--primary);
|
||||
background: var(--primary);
|
||||
color: var(--on-primary, #fff);
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.member-modal-secondary-btn {
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
|
||||
&:hover {
|
||||
border-color: color-mix(in srgb, var(--primary) 40%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 8%, var(--bg-secondary));
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-mode="dark"] {
|
||||
.member-modal,
|
||||
.member-export-modal,
|
||||
.member-result-modal {
|
||||
background: color-mix(in srgb, var(--bg-secondary) 94%, transparent);
|
||||
border-color: color-mix(in srgb, var(--border-color) 85%, transparent);
|
||||
}
|
||||
|
||||
.members-grid .member-card,
|
||||
.rankings-list .ranking-item,
|
||||
.member-export-panel .member-message-item,
|
||||
.media-stats .media-layout .media-legend,
|
||||
.media-stats .stat-card,
|
||||
.media-stats .charts-grid .chart-card {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.home-page {
|
||||
height: 100%;
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -8,105 +7,29 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.home-bg-blobs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: blur(80px);
|
||||
z-index: 0;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blob {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
animation: moveBlob 20s infinite alternate ease-in-out;
|
||||
}
|
||||
|
||||
.blob-1 {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: rgba(var(--primary-rgb), 0.25);
|
||||
top: -100px;
|
||||
left: -50px;
|
||||
animation-duration: 25s;
|
||||
}
|
||||
|
||||
.blob-2 {
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
background: rgba(var(--primary-rgb), 0.15);
|
||||
bottom: -50px;
|
||||
right: -50px;
|
||||
animation-duration: 30s;
|
||||
animation-delay: -5s;
|
||||
}
|
||||
|
||||
.blob-3 {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
top: 40%;
|
||||
left: 30%;
|
||||
animation-duration: 22s;
|
||||
animation-delay: -10s;
|
||||
}
|
||||
|
||||
[data-mode="dark"] .blob-3 {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.home-content {
|
||||
z-index: 1;
|
||||
animation: fadeScaleUp 1s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
max-width: 640px;
|
||||
width: 100%;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 64px;
|
||||
font-weight: 800;
|
||||
margin: 0 0 16px;
|
||||
.home-title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: -2px;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, rgba(var(--primary-rgb), 0.6) 100%);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 18px;
|
||||
.home-subtitle {
|
||||
font-size: 16px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 520px;
|
||||
margin: 0 auto;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@keyframes moveBlob {
|
||||
from {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate(100px, 50px) scale(1.1);
|
||||
@media (max-width: 480px) {
|
||||
.home-title {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeScaleUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,11 @@
|
||||
import { FolderOpen, ShieldCheck, Sparkles, Waves } from 'lucide-react'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import './HomePage.scss'
|
||||
|
||||
function HomePage() {
|
||||
return (
|
||||
<div className="home-page">
|
||||
<div className="home-bg-blobs">
|
||||
<div className="blob blob-1"></div>
|
||||
<div className="blob blob-2"></div>
|
||||
<div className="blob blob-3"></div>
|
||||
</div>
|
||||
|
||||
<div className="home-content">
|
||||
<div className="hero">
|
||||
<h1 className="hero-title">WeFlow</h1>
|
||||
<p className="hero-subtitle">每一条消息的背后,都藏着一段温暖的时光</p>
|
||||
</div>
|
||||
<h1 className="home-title">WeFlow</h1>
|
||||
<p className="home-subtitle">每一条消息的背后,都藏着一段温暖的时光</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.insight-inbox-page {
|
||||
--insight-panel-width: 360px;
|
||||
--insight-panel-width: 328px;
|
||||
--insight-card-bg: var(--bg-secondary);
|
||||
display: flex;
|
||||
height: calc(100% + 48px);
|
||||
@@ -14,15 +14,15 @@
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 18px 24px 14px;
|
||||
padding: 16px 20px 12px;
|
||||
}
|
||||
|
||||
.insight-inbox-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 0 4px 12px;
|
||||
gap: 14px;
|
||||
padding: 0 2px 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.insight-inbox-title-line {
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-size: 21px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@@ -82,7 +82,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
|
||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
@@ -92,15 +92,15 @@
|
||||
}
|
||||
|
||||
.insight-icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.insight-action-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
|
||||
&.code {
|
||||
color: var(--primary);
|
||||
@@ -118,10 +118,10 @@
|
||||
}
|
||||
|
||||
.insight-focus-bar {
|
||||
margin: 12px 4px 0;
|
||||
padding: 9px 12px;
|
||||
margin: 10px 2px 0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid rgba(91, 147, 144, 0.22);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(91, 147, 144, 0.08);
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
@@ -143,14 +143,14 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 16px 4px 22px;
|
||||
padding: 12px 2px 18px;
|
||||
}
|
||||
|
||||
.insight-date-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.insight-date-label {
|
||||
@@ -158,27 +158,26 @@
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
width: fit-content;
|
||||
padding: 5px 10px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--bg-primary) 86%, transparent);
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.insight-card {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 18px;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
background: var(--insight-card-bg);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(91, 147, 144, 0.28);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.07);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 86%, var(--bg-primary));
|
||||
}
|
||||
|
||||
&.unread {
|
||||
@@ -187,7 +186,7 @@
|
||||
|
||||
&.focused {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(91, 147, 144, 0.14), 0 12px 32px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: inset 0 0 0 1px rgba(91, 147, 144, 0.28);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +199,7 @@
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.insight-card-header {
|
||||
@@ -277,8 +276,8 @@
|
||||
.insight-body {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 15px;
|
||||
line-height: 1.72;
|
||||
font-size: 14px;
|
||||
line-height: 1.66;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -286,14 +285,14 @@
|
||||
.insight-filter-panel {
|
||||
width: var(--insight-panel-width);
|
||||
flex-shrink: 0;
|
||||
padding: 24px 24px 18px;
|
||||
padding: 18px 18px 14px;
|
||||
border-left: 1px solid var(--border-color);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 70%, var(--bg-primary));
|
||||
background: var(--bg-secondary);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.insight-filter-header {
|
||||
margin-bottom: 18px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
@@ -304,10 +303,10 @@
|
||||
|
||||
.insight-filter-widget {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
background: var(--bg-secondary);
|
||||
padding: 14px;
|
||||
margin-bottom: 14px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.insight-widget-title {
|
||||
@@ -330,14 +329,14 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border-radius: 9px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0 9px;
|
||||
|
||||
input {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
height: 38px;
|
||||
height: 34px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
@@ -410,12 +409,12 @@
|
||||
|
||||
.insight-contact-row {
|
||||
width: 100%;
|
||||
min-height: 42px;
|
||||
min-height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
padding: 7px 8px;
|
||||
@@ -478,7 +477,6 @@
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -490,9 +488,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.14);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -501,7 +499,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px 18px;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-tertiary);
|
||||
|
||||
@@ -549,7 +547,7 @@
|
||||
.insight-log-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 18px;
|
||||
padding: 16px;
|
||||
background: var(--bg-primary);
|
||||
|
||||
section {
|
||||
@@ -567,7 +565,7 @@
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
gap: 24px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
animation: footprintPageEnter 0.4s ease-out;
|
||||
|
||||
.card-surface {
|
||||
/* Removing border and strong shadows, just subtle background if any */
|
||||
@@ -31,7 +30,7 @@
|
||||
gap: 20px;
|
||||
padding: 10px 0 20px 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 40%, transparent);
|
||||
animation: footprintFadeSlideUp 0.3s ease both;
|
||||
animation: footprintFadeSlideUp 0.22s ease both;
|
||||
}
|
||||
|
||||
.footprint-title-wrap {
|
||||
@@ -41,10 +40,10 @@
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 26px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.3px;
|
||||
letter-spacing: 0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
@@ -93,7 +92,7 @@
|
||||
&.active {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +116,9 @@
|
||||
padding: 6px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.02) inset;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 12%, transparent);
|
||||
@@ -135,7 +134,7 @@
|
||||
&::-webkit-calendar-picker-indicator {
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s ease;
|
||||
transition: opacity 0.15s ease, background 0.15s ease;
|
||||
padding: 4px;
|
||||
margin-left: 4px;
|
||||
margin-right: -4px;
|
||||
@@ -165,7 +164,7 @@
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
color: var(--text-tertiary);
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:focus-within {
|
||||
background: color-mix(in srgb, var(--primary) 8%, transparent);
|
||||
@@ -201,7 +200,7 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--text-tertiary) 15%, transparent);
|
||||
@@ -217,8 +216,8 @@
|
||||
.kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
gap: 16px;
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 40%, transparent);
|
||||
}
|
||||
|
||||
@@ -241,11 +240,11 @@
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 32px;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: -0.5px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
small {
|
||||
@@ -259,7 +258,7 @@
|
||||
}
|
||||
|
||||
.footprint-ai-result {
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
background: color-mix(in srgb, var(--text-tertiary) 8%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 40%, transparent);
|
||||
@@ -360,7 +359,7 @@
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
@@ -688,11 +687,11 @@
|
||||
|
||||
.footprint-export-modal {
|
||||
width: min(520px, 100%);
|
||||
border-radius: 16px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 60%, transparent);
|
||||
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.2);
|
||||
padding: 22px 22px 18px;
|
||||
box-shadow: 0 14px 38px rgba(0, 0, 0, 0.16);
|
||||
padding: 18px 18px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
@@ -700,7 +699,7 @@
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@@ -716,7 +715,7 @@
|
||||
.export-modal-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -740,8 +739,8 @@
|
||||
.export-modal-path {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
padding: 9px 11px;
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
.resources-page.stream-rebuild {
|
||||
--stream-columns: 4;
|
||||
--stream-grid-gap: 12px;
|
||||
--stream-card-width: 272px;
|
||||
--stream-card-height: 356px;
|
||||
--stream-visual-height: 236px;
|
||||
--stream-grid-gap: 10px;
|
||||
--stream-card-width: 248px;
|
||||
--stream-card-height: 318px;
|
||||
--stream-visual-height: 210px;
|
||||
--stream-slot-width: calc(var(--stream-card-width) + var(--stream-grid-gap));
|
||||
--stream-slot-height: calc(var(--stream-card-height) + var(--stream-grid-gap));
|
||||
--stream-grid-width: calc(var(--stream-slot-width) * var(--stream-columns));
|
||||
height: calc(100% + 48px);
|
||||
margin: -24px;
|
||||
padding: 16px 18px;
|
||||
padding: 14px 16px;
|
||||
position: relative;
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.stream-toolbar {
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 78%, transparent);
|
||||
background: var(--card-bg, #f8f9fb);
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--bg-secondary) 85%, transparent);
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
@@ -49,16 +49,16 @@
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary, #5f6674);
|
||||
border-radius: 9px;
|
||||
padding: 7px 14px;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&.active {
|
||||
background: color-mix(in srgb, var(--primary) 18%, var(--card-bg));
|
||||
color: var(--text-primary, #1c2230);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 45%, transparent);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,9 @@
|
||||
border: 1px solid color-mix(in srgb, var(--border-color, #d2d7df) 95%, transparent);
|
||||
background: var(--bg-secondary, #f3f5f8);
|
||||
color: var(--text-secondary, #566074);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
padding: 0 10px;
|
||||
min-height: 36px;
|
||||
min-height: 34px;
|
||||
box-sizing: border-box;
|
||||
|
||||
svg {
|
||||
@@ -103,8 +103,8 @@
|
||||
color: var(--text-primary, #1c2230);
|
||||
font-size: 13px;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-family: "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
|
||||
appearance: none;
|
||||
}
|
||||
@@ -129,8 +129,8 @@
|
||||
border: 1px solid var(--border-color);
|
||||
background: transparent;
|
||||
color: var(--text-secondary, #5f6674);
|
||||
border-radius: 10px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
height: 34px;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -152,8 +152,8 @@
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary, #f3f5f8);
|
||||
color: var(--text-secondary, #5f6674);
|
||||
border-radius: 10px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
display: inline-flex;
|
||||
@@ -184,9 +184,9 @@
|
||||
}
|
||||
|
||||
.stream-state {
|
||||
height: 120px;
|
||||
height: 104px;
|
||||
border: 1px dashed var(--border-color);
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
@@ -204,7 +204,7 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 80%, transparent);
|
||||
border-radius: 16px;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--card-bg) 94%, transparent);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -226,7 +226,7 @@
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
padding: 10px 0 2px;
|
||||
padding: 8px 0 2px;
|
||||
width: var(--stream-grid-width);
|
||||
min-width: var(--stream-grid-width);
|
||||
max-width: var(--stream-grid-width);
|
||||
@@ -257,7 +257,7 @@
|
||||
height: 100%;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 14px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.16s ease;
|
||||
position: relative;
|
||||
@@ -288,7 +288,7 @@
|
||||
z-index: 4;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 9px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid color-mix(in srgb, var(--danger) 48%, var(--border-color));
|
||||
color: var(--danger);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 90%, transparent);
|
||||
@@ -297,9 +297,9 @@
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transform: translateY(-2px) scale(0.96);
|
||||
transform: none;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.16s ease, transform 0.16s ease;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.floating-info {
|
||||
@@ -317,7 +317,7 @@
|
||||
.media-card:hover .floating-delete,
|
||||
.media-card:focus-within .floating-delete {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
transform: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 45%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--bg-secondary) 90%, transparent);
|
||||
color: var(--text-primary);
|
||||
border-radius: 9px;
|
||||
border-radius: 6px;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
@@ -408,15 +408,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(140deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.04));
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -40%;
|
||||
background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, 0.35) 50%, transparent 65%);
|
||||
animation: decrypt-sheen 1.6s linear infinite;
|
||||
background: none;
|
||||
animation: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -429,16 +429,14 @@
|
||||
border-radius: 999px;
|
||||
border: 2px solid rgba(15, 23, 42, 0.2);
|
||||
border-top-color: color-mix(in srgb, var(--primary) 78%, #ffffff);
|
||||
animation: decrypt-spin 0.85s linear infinite, decrypt-pulse 1.2s ease-in-out infinite;
|
||||
box-shadow:
|
||||
0 0 0 8px rgba(255, 255, 255, 0.26),
|
||||
0 10px 24px rgba(15, 23, 42, 0.12);
|
||||
animation: decrypt-spin 0.85s linear infinite;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
padding: 9px 10px 8px;
|
||||
min-height: 66px;
|
||||
padding: 8px 10px;
|
||||
min-height: 58px;
|
||||
margin-top: auto;
|
||||
cursor: pointer;
|
||||
border-top: 1px solid color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
@@ -517,8 +515,8 @@
|
||||
width: min(420px, calc(100% - 32px));
|
||||
background: var(--dialog-surface);
|
||||
border: 1px solid color-mix(in srgb, var(--border-color) 90%, transparent);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.18);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2212,23 +2212,32 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
/>
|
||||
</div>
|
||||
<div className="anti-revoke-toolbar-actions">
|
||||
<div className="anti-revoke-btn-group">
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => void handleRefreshAntiRevokeStatus()} disabled={busy}>
|
||||
<RefreshCw size={14} /> {isAntiRevokeRefreshing ? '刷新中...' : '刷新状态'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="anti-revoke-btn-group">
|
||||
<button className="btn btn-secondary btn-sm" onClick={selectAllFiltered} disabled={busy || filteredSessionIds.length === 0 || allFilteredSelected}>
|
||||
全选
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={clearSelection} disabled={busy || selectedCount === 0}>
|
||||
清空选择
|
||||
</button>
|
||||
</div>
|
||||
<button className="btn btn-secondary btn-sm" onClick={() => void handleRefreshAntiRevokeStatus()} disabled={busy}>
|
||||
<RefreshCw size={14} /> {isAntiRevokeRefreshing ? '刷新中...' : '刷新状态'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="anti-revoke-selection-strip">
|
||||
<div className="anti-revoke-selected-count">
|
||||
<span>已选 <strong>{selectedCount}</strong> 个会话</span>
|
||||
<span>筛选命中 <strong>{selectedInFilteredCount}</strong> / {filteredSessionIds.length}</span>
|
||||
</div>
|
||||
<div className="anti-revoke-selection-actions">
|
||||
<button className="btn btn-secondary btn-sm" onClick={selectAllFiltered} disabled={busy || filteredSessionIds.length === 0 || allFilteredSelected}>
|
||||
全选
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={clearSelection} disabled={busy || selectedCount === 0}>
|
||||
清空选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="anti-revoke-batch-actions">
|
||||
<div className="anti-revoke-batch-copy">
|
||||
<span className="anti-revoke-section-label">批量部署</span>
|
||||
<span>对已选会话执行防撤回安装或卸载</span>
|
||||
</div>
|
||||
<div className="anti-revoke-btn-group anti-revoke-batch-btns">
|
||||
<button className="btn btn-primary btn-sm" onClick={() => void handleInstallAntiRevokeTriggers()} disabled={busy || selectedCount === 0}>
|
||||
{isAntiRevokeInstalling ? '安装中...' : '批量安装'}
|
||||
@@ -2237,10 +2246,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
{isAntiRevokeUninstalling ? '卸载中...' : '批量卸载'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="anti-revoke-selected-count">
|
||||
<span>已选 <strong>{selectedCount}</strong> 个会话</span>
|
||||
<span>筛选命中 <strong>{selectedInFilteredCount}</strong> / {filteredSessionIds.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2539,11 +2544,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
|
||||
const renderModelsTab = () => (
|
||||
<div className="tab-content">
|
||||
<div className="form-group">
|
||||
<label>模型管理</label>
|
||||
<span className="form-hint">管理语音识别模型</span>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>语音识别模型 (Whisper)</label>
|
||||
<span className="form-hint">用于语音消息转文字功能</span>
|
||||
@@ -2561,12 +2561,6 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
) : (
|
||||
<span className="status-indicator warning">未安装</span>
|
||||
)}
|
||||
{resolvedWhisperModelPath && (
|
||||
<div className="model-path-block">
|
||||
<span className="path-label">模型目录</span>
|
||||
<div className="path-text" title={resolvedWhisperModelPath}>{resolvedWhisperModelPath}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{(!whisperModelStatus?.exists || isWhisperDownloading) && (
|
||||
@@ -2597,25 +2591,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="sub-setting">
|
||||
<div className="sub-label">自定义模型目录</div>
|
||||
<div className="path-selector">
|
||||
<div className="model-directory-control">
|
||||
<input
|
||||
type="text"
|
||||
value={whisperModelDir}
|
||||
value={resolvedWhisperModelPath}
|
||||
readOnly
|
||||
placeholder="默认目录"
|
||||
title={resolvedWhisperModelPath || '默认目录'}
|
||||
/>
|
||||
<button className="btn-icon" onClick={handleSelectWhisperModelDir} title="选择目录">
|
||||
<FolderOpen size={18} />
|
||||
<button className="btn btn-secondary btn-sm" onClick={handleSelectWhisperModelDir} title="选择自定义目录">
|
||||
<FolderOpen size={14} /> 选择自定义目录
|
||||
</button>
|
||||
<button className="btn btn-secondary btn-sm" onClick={handleResetWhisperModelDir} disabled={!whisperModelDir} title="恢复默认">
|
||||
<RotateCcw size={14} /> 恢复默认
|
||||
</button>
|
||||
{whisperModelDir && (
|
||||
<button className="btn-icon danger" onClick={handleResetWhisperModelDir} title="重置为默认">
|
||||
<RotateCcw size={18} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* Global Variables */
|
||||
:root {
|
||||
--sns-max-width: 800px;
|
||||
--sns-panel-width: 380px;
|
||||
--sns-max-width: 920px;
|
||||
--sns-panel-width: 320px;
|
||||
--sns-bg-color: var(--bg-primary);
|
||||
--sns-card-bg: var(--bg-secondary);
|
||||
--sns-border-radius-lg: 16px;
|
||||
--sns-border-radius-md: 12px;
|
||||
--sns-border-radius-sm: 8px;
|
||||
--sns-card-bg: transparent;
|
||||
--sns-border-radius-lg: 10px;
|
||||
--sns-border-radius-md: 8px;
|
||||
--sns-border-radius-sm: 6px;
|
||||
--sns-media-cell: 88px;
|
||||
}
|
||||
|
||||
.sns-page-layout {
|
||||
@@ -33,7 +34,7 @@
|
||||
.sns-feed-container {
|
||||
width: 100%;
|
||||
max-width: var(--sns-max-width);
|
||||
padding: 10px 24px 12px 24px;
|
||||
padding: 14px 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
@@ -45,13 +46,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 4px;
|
||||
gap: 16px;
|
||||
margin-bottom: 0;
|
||||
padding: 2px 2px 10px;
|
||||
z-index: 2;
|
||||
background: var(--sns-bg-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-top: 4px;
|
||||
padding-bottom: 6px;
|
||||
|
||||
.feed-header-main {
|
||||
display: flex;
|
||||
@@ -70,9 +70,9 @@
|
||||
.feed-stats-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.jump-calendar-anchor {
|
||||
@@ -186,24 +186,28 @@
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: var(--bg-tertiary);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
padding: 8px;
|
||||
padding: 0;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--hover-bg);
|
||||
color: var(--primary);
|
||||
transform: scale(1.05);
|
||||
background: var(--bg-hover);
|
||||
border-color: color-mix(in srgb, var(--text-tertiary) 34%, var(--border-color));
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.spinning {
|
||||
@@ -217,20 +221,21 @@
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
background: var(--bg-tertiary);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
padding: 8px 10px;
|
||||
min-height: 32px;
|
||||
padding: 0 9px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--hover-bg);
|
||||
color: var(--primary);
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--primary);
|
||||
background: rgba(var(--primary-rgb), 0.08);
|
||||
background: rgba(var(--primary-rgb), 0.07);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
@@ -256,19 +261,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sns-posts-scroll {
|
||||
.sns-posts-stage {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sns-posts-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 16px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.sns-post-row {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.feed-contact-filter-bar {
|
||||
margin: 10px 4px 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 28%, var(--border-color));
|
||||
border-radius: 12px;
|
||||
background: rgba(var(--primary-rgb), 0.08);
|
||||
margin: 8px 0 0;
|
||||
padding: 7px 9px;
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 20%, var(--border-color));
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
background: rgba(var(--primary-rgb), 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -281,7 +295,7 @@
|
||||
}
|
||||
|
||||
.feed-contact-filter-summary {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
min-width: 0;
|
||||
@@ -293,7 +307,7 @@
|
||||
background: transparent;
|
||||
color: var(--primary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
@@ -308,7 +322,7 @@
|
||||
.posts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* =========================================
|
||||
@@ -316,19 +330,21 @@
|
||||
========================================= */
|
||||
.sns-post-item {
|
||||
background: var(--sns-card-bg);
|
||||
border-radius: var(--sns-border-radius-lg);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 20px;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent);
|
||||
padding: 10px 6px 12px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
|
||||
gap: 12px;
|
||||
transition: background 0.15s ease;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.06);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 72%, transparent);
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
&.post-deleted {
|
||||
@@ -341,9 +357,9 @@
|
||||
gap: 4px;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
color: #ff4d4f;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 3px 8px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
@@ -362,12 +378,12 @@
|
||||
}
|
||||
|
||||
.avatar-trigger {
|
||||
border-radius: 12px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.1);
|
||||
opacity: 0.86;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -386,7 +402,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.post-author-info {
|
||||
display: flex;
|
||||
@@ -395,12 +412,10 @@
|
||||
overflow: hidden;
|
||||
|
||||
.author-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
/* Changed to primary from accent for cleaner look, or keep accent */
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
margin-bottom: 2px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.author-name-trigger {
|
||||
@@ -430,13 +445,14 @@
|
||||
.post-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
.post-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -451,9 +467,11 @@
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -462,8 +480,7 @@
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--text-secondary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
&.delete-btn:hover {
|
||||
@@ -751,20 +768,20 @@
|
||||
}
|
||||
|
||||
.post-text {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
line-height: 1.48;
|
||||
color: var(--text-primary);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin-bottom: 12px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.post-location {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
margin: -4px 0 12px;
|
||||
font-size: 13px;
|
||||
margin: -2px 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -780,7 +797,7 @@
|
||||
}
|
||||
|
||||
.post-media-container {
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.post-link-card {
|
||||
@@ -788,14 +805,14 @@
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
padding: 10px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 12px;
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--primary-rgb), 0.08);
|
||||
@@ -803,8 +820,8 @@
|
||||
}
|
||||
|
||||
.link-thumb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
@@ -830,7 +847,7 @@
|
||||
min-width: 0;
|
||||
|
||||
.link-title {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
@@ -839,7 +856,7 @@
|
||||
}
|
||||
|
||||
.link-url {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -851,15 +868,15 @@
|
||||
}
|
||||
|
||||
.post-interactions {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed var(--border-color);
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
font-size: 12px;
|
||||
|
||||
.likes-block {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
color: var(--primary);
|
||||
font-weight: 500;
|
||||
|
||||
@@ -871,11 +888,11 @@
|
||||
.comments-block {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
padding: 8px 12px;
|
||||
padding: 6px 8px;
|
||||
|
||||
.comment-row {
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 3px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-secondary);
|
||||
|
||||
&:last-child {
|
||||
@@ -912,13 +929,13 @@
|
||||
========================================= */
|
||||
.sns-media-grid {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
|
||||
&.grid-1 .sns-media-item {
|
||||
width: fit-content;
|
||||
height: auto;
|
||||
max-width: 300px;
|
||||
max-height: 480px;
|
||||
max-width: 270px;
|
||||
max-height: 340px;
|
||||
aspect-ratio: auto;
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
|
||||
@@ -930,7 +947,7 @@
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
max-height: 480px;
|
||||
max-height: 340px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
@@ -941,26 +958,25 @@
|
||||
|
||||
&.grid-2,
|
||||
&.grid-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
max-width: 320px;
|
||||
grid-template-columns: repeat(2, var(--sns-media-cell));
|
||||
max-width: calc((var(--sns-media-cell) * 2) + 4px);
|
||||
}
|
||||
|
||||
&.grid-3,
|
||||
&.grid-6,
|
||||
&.grid-9 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
max-width: 320px;
|
||||
grid-template-columns: repeat(3, var(--sns-media-cell));
|
||||
max-width: calc((var(--sns-media-cell) * 3) + 8px);
|
||||
}
|
||||
|
||||
.sns-media-item {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
/* Consistent radius for grid items */
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
background: var(--bg-tertiary);
|
||||
transition: opacity 0.2s;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
@@ -972,7 +988,7 @@
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
animation: fade-in 0.3s ease;
|
||||
animation: fade-in 0.2s ease;
|
||||
}
|
||||
|
||||
.media-badge {
|
||||
@@ -984,7 +1000,6 @@
|
||||
height: 32px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -993,8 +1008,8 @@
|
||||
/* Let clicks pass through badge to item */
|
||||
|
||||
&.live {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
left: auto;
|
||||
transform: none;
|
||||
width: 24px;
|
||||
@@ -1015,30 +1030,27 @@
|
||||
gap: 8px;
|
||||
z-index: 10;
|
||||
font-size: 12px;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.media-download-btn {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.2s;
|
||||
transition: opacity 0.15s ease, background 0.15s ease;
|
||||
z-index: 5;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Increase click area */
|
||||
@@ -1092,8 +1104,8 @@
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
gap: 24px;
|
||||
padding: 16px 14px;
|
||||
gap: 14px;
|
||||
z-index: 10;
|
||||
|
||||
.filter-header {
|
||||
@@ -1102,8 +1114,8 @@
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
font-weight: 650;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -1112,10 +1124,16 @@
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--text-tertiary);
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
animation: spin 0.5s ease;
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1123,30 +1141,30 @@
|
||||
.filter-widgets {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.filter-widget {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
|
||||
.widget-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
|
||||
svg {
|
||||
color: var(--primary);
|
||||
opacity: 0.8;
|
||||
color: var(--text-tertiary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.badge {
|
||||
@@ -1161,7 +1179,7 @@
|
||||
.widget-header-summary {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
color: var(--text-tertiary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1183,17 +1201,16 @@
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
padding: 10px 30px 10px 12px;
|
||||
height: 34px;
|
||||
padding: 0 30px 0 10px;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
|
||||
&:focus {
|
||||
background: var(--bg-primary);
|
||||
background: var(--bg-secondary);
|
||||
border-color: transparent;
|
||||
/* Explicitly transparent */
|
||||
outline: none;
|
||||
/* Ensure no outline */
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
@@ -1218,14 +1235,16 @@
|
||||
min-height: 300px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 12px;
|
||||
|
||||
.widget-header {
|
||||
padding: 16px 16px 12px 16px;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.contact-search-bar {
|
||||
padding: 0 16px 12px 16px;
|
||||
padding: 0 0 10px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
@@ -1233,22 +1252,23 @@
|
||||
width: 100%;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
padding: 8px 32px 8px 12px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
height: 32px;
|
||||
padding: 0 32px 0 10px;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--primary);
|
||||
background: var(--bg-secondary);
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
top: 9px;
|
||||
color: var(--text-tertiary);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
@@ -1256,8 +1276,8 @@
|
||||
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
top: 8px;
|
||||
right: 10px;
|
||||
top: 9px;
|
||||
color: var(--text-tertiary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -1270,7 +1290,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 72%, transparent);
|
||||
}
|
||||
|
||||
@@ -1287,9 +1307,9 @@
|
||||
background: color-mix(in srgb, var(--bg-primary) 84%, rgba(var(--primary-rgb), 0.08));
|
||||
color: var(--text-secondary);
|
||||
border-radius: 999px;
|
||||
padding: 5px 10px;
|
||||
padding: 4px 9px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
|
||||
@@ -1312,7 +1332,7 @@
|
||||
}
|
||||
|
||||
.contact-count-progress {
|
||||
padding: 8px 16px 10px;
|
||||
padding: 7px 0 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
border-bottom: 1px dashed color-mix(in srgb, var(--border-color) 70%, transparent);
|
||||
@@ -1320,7 +1340,7 @@
|
||||
}
|
||||
|
||||
.contact-interaction-hint {
|
||||
padding: 10px 16px 0;
|
||||
padding: 8px 0 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-tertiary);
|
||||
@@ -1328,23 +1348,20 @@
|
||||
|
||||
.contact-list-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
min-height: 0;
|
||||
padding: 8px 0 0;
|
||||
|
||||
.contact-list-virtuoso {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contact-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
gap: 4px;
|
||||
min-height: 40px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
transition: background 0.15s ease;
|
||||
|
||||
&.is-selected .contact-main-btn {
|
||||
background: rgba(var(--primary-rgb), 0.06);
|
||||
@@ -1362,7 +1379,7 @@
|
||||
}
|
||||
|
||||
.contact-select-btn {
|
||||
width: 32px;
|
||||
width: 28px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
border: none;
|
||||
@@ -1373,7 +1390,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
@@ -1390,17 +1407,17 @@
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
gap: 8px;
|
||||
padding: 5px 6px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, border-color 0.2s ease;
|
||||
transition: background 0.15s ease, border-color 0.15s ease;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background: var(--hover-bg);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1412,7 +1429,7 @@
|
||||
gap: 2px;
|
||||
|
||||
.contact-name {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -1422,7 +1439,7 @@
|
||||
|
||||
.contact-post-count-wrap {
|
||||
margin-left: 8px;
|
||||
min-width: 46px;
|
||||
min-width: 42px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
@@ -1430,7 +1447,7 @@
|
||||
}
|
||||
|
||||
.contact-post-count {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
@@ -1453,9 +1470,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px 14px;
|
||||
padding: 10px 0 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
background: color-mix(in srgb, var(--bg-primary) 86%, var(--bg-secondary));
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.contact-batch-summary {
|
||||
@@ -1470,8 +1487,8 @@
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--sns-border-radius-md);
|
||||
height: 32px;
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1483,7 +1500,7 @@
|
||||
|
||||
&:hover {
|
||||
border-color: var(--text-tertiary);
|
||||
background: var(--hover-bg);
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -1508,9 +1525,9 @@
|
||||
========================================= */
|
||||
.status-indicator {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
padding: 10px 8px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1520,8 +1537,10 @@
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
border: 1px solid color-mix(in srgb, var(--primary) 18%, var(--border-color));
|
||||
border-radius: var(--sns-border-radius-sm);
|
||||
margin: 0 24px;
|
||||
margin: 0 0 8px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,45 +1549,44 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120px 0;
|
||||
padding: 72px 0;
|
||||
|
||||
.loading-pulse {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
gap: 12px;
|
||||
|
||||
.pulse-circle {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary, #576b95);
|
||||
opacity: 0.25;
|
||||
animation: pulse-ring 1.4s ease-in-out infinite;
|
||||
opacity: 0.4;
|
||||
animation: pulse-ring 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0% {
|
||||
transform: scale(0.6);
|
||||
opacity: 0.15;
|
||||
transform: scale(0.85);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.0);
|
||||
opacity: 0.35;
|
||||
transform: scale(1);
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.6);
|
||||
opacity: 0.15;
|
||||
transform: scale(0.85);
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,20 +1594,25 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60px 0;
|
||||
padding: 56px 0;
|
||||
color: var(--text-tertiary);
|
||||
|
||||
.no-results-icon {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.reset-inline {
|
||||
margin-top: 16px;
|
||||
margin-top: 12px;
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -2760,7 +2783,7 @@
|
||||
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--hover-bg);
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -2902,7 +2925,7 @@
|
||||
color: var(--text-secondary);
|
||||
|
||||
&:hover {
|
||||
background: var(--hover-bg);
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from 'react'
|
||||
import { RefreshCw, Search, X, Download, FolderOpen, FileJson, FileText, Image, CheckCircle, AlertCircle, Calendar, Info, Shield, ShieldOff, Loader2, Pause, Play, Square } from 'lucide-react'
|
||||
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso'
|
||||
import './SnsPage.scss'
|
||||
import { SnsPost } from '../types/sns'
|
||||
import { SnsPostItem } from '../components/Sns/SnsPostItem'
|
||||
@@ -229,7 +230,8 @@ export default function SnsPage() {
|
||||
const [cacheMigrationDone, setCacheMigrationDone] = useState(false)
|
||||
const [cacheMigrationError, setCacheMigrationError] = useState<string | null>(null)
|
||||
|
||||
const postsContainerRef = useRef<HTMLDivElement>(null)
|
||||
const postsContainerRef = useRef<HTMLElement | null>(null)
|
||||
const postsVirtuosoRef = useRef<VirtuosoHandle | null>(null)
|
||||
const jumpCalendarWrapRef = useRef<HTMLDivElement | null>(null)
|
||||
const [hasNewer, setHasNewer] = useState(false)
|
||||
const [loadingNewer, setLoadingNewer] = useState(false)
|
||||
@@ -1110,6 +1112,7 @@ export default function SnsPage() {
|
||||
setHasNewer(false);
|
||||
}
|
||||
|
||||
postsVirtuosoRef.current?.scrollToIndex({ index: 0, align: 'start', behavior: 'auto' })
|
||||
if (postsContainerRef.current) {
|
||||
postsContainerRef.current.scrollTop = 0
|
||||
}
|
||||
@@ -1764,23 +1767,61 @@ export default function SnsPage() {
|
||||
loadPosts({ reset: true })
|
||||
}, [loadPosts, selectedContactUsernamesKey])
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.currentTarget
|
||||
if (scrollHeight - scrollTop - clientHeight < 400 && hasMore && !loading && !loadingNewer) {
|
||||
loadPosts({ direction: 'older' })
|
||||
}
|
||||
if (scrollTop < 10 && hasNewer && !loading && !loadingNewer) {
|
||||
loadPosts({ direction: 'newer' })
|
||||
}
|
||||
}
|
||||
const handlePostsEndReached = useCallback(() => {
|
||||
if (!hasMore || loading || loadingNewer) return
|
||||
void loadPosts({ direction: 'older' })
|
||||
}, [hasMore, loadPosts, loading, loadingNewer])
|
||||
|
||||
const handleWheel = (e: React.WheelEvent<HTMLDivElement>) => {
|
||||
const container = postsContainerRef.current
|
||||
if (!container) return
|
||||
if (e.deltaY < -20 && container.scrollTop <= 0 && hasNewer && !loading && !loadingNewer) {
|
||||
loadPosts({ direction: 'newer' })
|
||||
}
|
||||
}
|
||||
const renderPostItem = useCallback((_: number, post: SnsPost) => (
|
||||
<div className="sns-post-row">
|
||||
<SnsPostItem
|
||||
post={{ ...post, isProtected: triggerInstalled === true }}
|
||||
onPreview={(src, isVideo, liveVideoPath) => {
|
||||
if (isVideo) {
|
||||
void window.electronAPI.window.openVideoPlayerWindow(src)
|
||||
} else {
|
||||
void window.electronAPI.window.openImageViewerWindow(src, liveVideoPath || undefined)
|
||||
}
|
||||
}}
|
||||
onDebug={(p) => setDebugPost(p)}
|
||||
onDelete={handlePostDelete}
|
||||
onOpenAuthorPosts={openAuthorTimeline}
|
||||
/>
|
||||
</div>
|
||||
), [handlePostDelete, openAuthorTimeline, triggerInstalled])
|
||||
|
||||
const snsVirtuosoComponents = useMemo(() => ({
|
||||
Header: () => (
|
||||
<>
|
||||
{loadingNewer && (
|
||||
<div className="status-indicator loading-newer">
|
||||
<RefreshCw size={14} className="spinning" />
|
||||
<span>正在检查更新动态</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loadingNewer && hasNewer && (
|
||||
<button type="button" className="status-indicator newer-hint" onClick={() => void loadPosts({ direction: 'newer' })}>
|
||||
有新动态,点击查看
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
Footer: () => (
|
||||
<>
|
||||
{loading && visiblePosts.length > 0 && (
|
||||
<div className="status-indicator loading-more">
|
||||
<RefreshCw size={14} className="spinning" />
|
||||
<span>正在加载更多</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasMore && visiblePosts.length > 0 && (
|
||||
<div className="status-indicator no-more">已加载全部动态</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}), [hasMore, hasNewer, loadPosts, loading, loadingNewer, visiblePosts.length])
|
||||
|
||||
return (
|
||||
<div className="sns-page-layout">
|
||||
@@ -1940,62 +1981,19 @@ export default function SnsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="sns-posts-scroll" onScroll={handleScroll} onWheel={handleWheel} ref={postsContainerRef}>
|
||||
{loadingNewer && (
|
||||
<div className="status-indicator loading-newer">
|
||||
<RefreshCw size={16} className="spinning" />
|
||||
<span>正在检查更新的动态...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loadingNewer && hasNewer && (
|
||||
<div className="status-indicator newer-hint" onClick={() => loadPosts({ direction: 'newer' })}>
|
||||
有新动态,点击查看
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="posts-list">
|
||||
{visiblePosts.map(post => (
|
||||
<SnsPostItem
|
||||
key={post.id}
|
||||
post={{ ...post, isProtected: triggerInstalled === true }}
|
||||
onPreview={(src, isVideo, liveVideoPath) => {
|
||||
if (isVideo) {
|
||||
void window.electronAPI.window.openVideoPlayerWindow(src)
|
||||
} else {
|
||||
void window.electronAPI.window.openImageViewerWindow(src, liveVideoPath || undefined)
|
||||
}
|
||||
}}
|
||||
onDebug={(p) => setDebugPost(p)}
|
||||
onDelete={handlePostDelete}
|
||||
onOpenAuthorPosts={openAuthorTimeline}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="sns-posts-stage">
|
||||
{loading && visiblePosts.length === 0 && (
|
||||
<div className="initial-loading">
|
||||
<div className="loading-pulse">
|
||||
<div className="pulse-circle"></div>
|
||||
<span>正在加载朋友圈...</span>
|
||||
<span>正在加载动态</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && visiblePosts.length > 0 && (
|
||||
<div className="status-indicator loading-more">
|
||||
<RefreshCw size={16} className="spinning" />
|
||||
<span>正在加载更多...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasMore && visiblePosts.length > 0 && (
|
||||
<div className="status-indicator no-more">或许过往已无可溯洄,但好在还有可以与你相遇的明天</div>
|
||||
)}
|
||||
|
||||
{!loading && visiblePosts.length === 0 && (
|
||||
<div className="no-results">
|
||||
<div className="no-results-icon"><Search size={48} /></div>
|
||||
<div className="no-results-icon"><Search size={28} /></div>
|
||||
<p>未找到相关动态</p>
|
||||
{(searchKeyword || jumpTargetDate || selectedContactUsernames.length > 0) && (
|
||||
<button onClick={() => {
|
||||
@@ -2008,6 +2006,24 @@ export default function SnsPage() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{visiblePosts.length > 0 && (
|
||||
<Virtuoso
|
||||
ref={postsVirtuosoRef}
|
||||
className="sns-posts-scroll"
|
||||
data={visiblePosts}
|
||||
computeItemKey={(_, post) => post.id}
|
||||
itemContent={renderPostItem}
|
||||
components={snsVirtuosoComponents}
|
||||
endReached={handlePostsEndReached}
|
||||
scrollerRef={(ref) => {
|
||||
postsContainerRef.current = ref instanceof HTMLElement ? ref : null
|
||||
}}
|
||||
defaultItemHeight={220}
|
||||
increaseViewportBy={{ top: 260, bottom: 520 }}
|
||||
overscan={{ main: 900, reverse: 480 }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user