Skip to content

Commit ebf33c3

Browse files
committed
1. 新增功能,实现可拖拽侧边栏功能
1 parent 2c345f8 commit ebf33c3

8 files changed

Lines changed: 304 additions & 97 deletions

File tree

‎vue/src/App.vue‎

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,22 @@
77
<AppSkeletonLoader />
88
</div>
99

10-
<div v-else class="app-container" :class="appContainerClasses">
10+
<!-- ✅ MODIFICATION START: Bind dynamic style for sidebar width and a class for collapsed state -->
11+
<div
12+
v-else
13+
class="app-container"
14+
:class="appContainerClasses"
15+
:style="{ '--sidebar-width': `${uiStore.sidebarWidth}px` }"
16+
>
17+
<!-- ✅ MODIFICATION END -->
1118
<aside class="sidebar-container">
1219
<ChatList />
1320
</aside>
1421

22+
<!-- ✅ MODIFICATION START: Add SidebarResizer component between sidebar and main content -->
23+
<SidebarResizer />
24+
<!-- ✅ MODIFICATION END -->
25+
1526
<div class="main-content-wrapper">
1627
<main class="main-view-container">
1728
<ChatView v-if="chatStore.currentChatId" :key="chatStore.currentChatId" />
@@ -49,8 +60,6 @@
4960
v-if="uiStore.activeModal === 'locationPicker'"
5061
v-motion-pop
5162
/>
52-
<!-- --- [移除] --- -->
53-
<!-- BindManualConnectionModal is no longer needed -->
5463
<ScreenshotGuideModal
5564
v-if="uiStore.activeModal === 'screenshotGuide'"
5665
v-motion-pop
@@ -63,8 +72,6 @@
6372
v-if="uiStore.activeModal === 'confirmation'"
6473
v-motion-pop
6574
/>
66-
<!-- --- [移除] --- -->
67-
<!-- CommentModal is no longer a global modal -->
6875
</div>
6976
</Transition>
7077

@@ -120,12 +127,13 @@ import WelcomeHeader from '@/components/ChatView/WelcomeHeader.vue';
120127
import FloatingCallWidget from '@/components/Shared/FloatingCallWidget.vue';
121128
import SettingsModalSkeleton from '@/components/Shared/SettingsModalSkeleton.vue';
122129
import MobileGlobalHeader from '@/components/Shared/MobileGlobalHeader.vue';
130+
// ✅ MODIFICATION START: Import the new resizer component
131+
import SidebarResizer from '@/components/Shared/SidebarResizer.vue';
132+
// ✅ MODIFICATION END
123133
124134
const SettingsModal = defineAsyncComponent(() => import('@/components/Modals/SettingsModal.vue'));
125135
const NewContactModal = defineAsyncComponent(() => import('@/components/Modals/NewContactModal.vue'));
126136
const LocationPickerModal = defineAsyncComponent(() => import('@/components/Modals/LocationPickerModal.vue'));
127-
// --- [移除] ---
128-
// const BindManualConnectionModal = ...
129137
const ScreenshotEditor = defineAsyncComponent(() => import('@/components/Modals/ScreenshotEditor.vue'));
130138
const ScreenshotGuideModal = defineAsyncComponent(() => import('@/components/Modals/ScreenshotGuideModal.vue'));
131139
const IncomingCallModal = defineAsyncComponent(() => import('@/components/Modals/IncomingCallModal.vue'));
@@ -134,8 +142,6 @@ const MediaViewerModal = defineAsyncComponent(() => import('@/components/Modals/
134142
const LocationViewerModal = defineAsyncComponent(() => import('@/components/Modals/LocationViewerModal.vue'));
135143
const WorldMapModal = defineAsyncComponent(() => import('@/components/Modals/WorldMapModal.vue'));
136144
const ImageCropperModal = defineAsyncComponent(() => import('@/components/Modals/ImageCropperModal.vue'));
137-
// --- [移除] ---
138-
// const CommentModal = ... // No longer needed here
139145
140146
const userStore = useUserStore();
141147
const chatStore = useChatStore();
@@ -147,9 +153,17 @@ const memoryStore = useMemoryStore();
147153
148154
const globalAudioSink = ref(null);
149155
156+
// ✅ MODIFICATION START: Define sidebar collapsed threshold and computed property
157+
const SIDEBAR_COLLAPSED_THRESHOLD = 80;
158+
const isSidebarCollapsed = computed(() => uiStore.sidebarWidth < SIDEBAR_COLLAPSED_THRESHOLD);
159+
// ✅ MODIFICATION END
160+
150161
const appContainerClasses = computed(() => ({
151162
'details-panel-open': uiStore.isDetailsPanelOpen,
152-
'chat-active': uiStore.isChatViewActiveOnMobile
163+
'chat-active': uiStore.isChatViewActiveOnMobile,
164+
// ✅ MODIFICATION START: Add collapsed class dynamically
165+
'sidebar-collapsed': isSidebarCollapsed.value,
166+
// ✅ MODIFICATION END
153167
}));
154168
155169
const appRootClasses = computed(() => ({
@@ -178,8 +192,6 @@ onMounted(async () => {
178192
await memoryStore.init();
179193
callStore.setGlobalAudioElement(globalAudioSink.value);
180194
webrtcService.init(userStore.userId);
181-
// --- [移除] ---
182-
// eventBus.on('webrtc:manual-connection-ready', ...)
183195
} catch (error) {
184196
console.error("应用初始化失败:", error);
185197
} finally {
@@ -213,11 +225,9 @@ function handleBackButton() {
213225
} else if (uiStore.activeModal) {
214226
uiStore.hideModal();
215227
handled = true;
216-
// ✅ MODIFICATION START: Add check for EmojiPicker
217228
} else if (uiStore.isEmojiPickerVisible) {
218229
uiStore.toggleEmojiPicker(false);
219230
handled = true;
220-
// ✅ MODIFICATION END
221231
} else if (uiStore.isDetailsPanelOpen) {
222232
uiStore.toggleDetailsPanel(false);
223233
handled = true;
@@ -307,7 +317,18 @@ body {
307317
width: var(--sidebar-width);
308318
flex-shrink: 0;
309319
background-color: var(--color-background-panel);
320+
/* ✅ MODIFICATION START: Add smooth transition for width changes */
321+
transition: width 0.2s ease-out;
322+
will-change: width;
323+
/* ✅ MODIFICATION END */
310324
}
325+
326+
/* ✅ MODIFICATION START: Disable transition during drag for "跟手" effect */
327+
body.is-resizing .sidebar-container {
328+
transition: none;
329+
}
330+
/* ✅ MODIFICATION END */
331+
311332
.details-panel-container {
312333
background-color: var(--color-background-panel);
313334
border-left: 1px solid var(--color-border);

‎vue/src/assets/styles/_base.css‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ body {
2828
transition: background-color 0.3s var(--transition-easing), color 0.3s var(--transition-easing);
2929
}
3030

31+
/* --- ✅ MODIFICATION START: Global styles for resizing state --- */
32+
body.is-resizing {
33+
cursor: ew-resize !important;
34+
user-select: none !important;
35+
}
36+
body.is-resizing * {
37+
/* Prevent iframes or other elements from capturing mouse events during drag */
38+
pointer-events: none;
39+
}
40+
/* --- ✅ MODIFICATION END --- */
41+
3142
/* --- Scrollbar Styling (Telegram Style) --- */
3243
::-webkit-scrollbar {
3344
width: 6px;

‎vue/src/components/ChatList/ChatListItem.vue‎

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
<template>
2-
<!-- --- MODIFICATION START --- -->
32
<div
43
class="chat-list-item"
54
:class="{ active: isActive, 'special-contact': item.isSpecial, [item.id]: item.isSpecial, 'highlight-new-message': isHighlighted }"
65
>
7-
<!-- --- [动画] START: 为头像添加悬浮效果 --- -->
86
<Avatar :entity="item" :is-online="isOnline"
97
v-motion
108
:hovered="{ scale: 1.1 }"
119
:tapped="{ scale: 0.9 }"
1210
/>
13-
<!-- --- [动画] END --- -->
1411
<div class="chat-info">
1512
<div class="info-top">
1613
<span class="name">{{ item.name }}</span>
@@ -25,9 +22,7 @@
2522
</template>
2623

2724
<script setup>
28-
// --- MODIFICATION START ---
2925
import { computed, ref, watch } from 'vue';
30-
// --- MODIFICATION END ---
3126
import { useUserStore } from '@/stores/userStore';
3227
import Avatar from '@/components/Shared/Avatar.vue';
3328
import { formatDate } from '@/utils';
@@ -44,9 +39,7 @@ const props = defineProps({
4439
});
4540
4641
const userStore = useUserStore();
47-
// --- MODIFICATION START ---
4842
const isHighlighted = ref(false);
49-
// --- MODIFICATION END ---
5043
5144
const combinedStatus = computed(() => userStore.getContactCombinedStatus(props.item.id));
5245
@@ -63,7 +56,6 @@ const unreadCount = computed(() => {
6356
return props.item.unread > 99 ? '99+' : props.item.unread;
6457
});
6558
66-
// --- MODIFICATION START: Watch for new unread messages to trigger highlight animation ---
6759
watch(
6860
() => props.item.unread,
6961
(newUnread, oldUnread) => {
@@ -78,8 +70,6 @@ watch(
7870
}
7971
}
8072
);
81-
// --- MODIFICATION END ---
82-
8373
</script>
8474

8575
<style scoped>
@@ -88,17 +78,14 @@ watch(
8878
align-items: center;
8979
padding: var(--spacing-3) var(--spacing-4);
9080
cursor: pointer;
91-
/* --- [动画] START: 优化过渡效果 --- */
9281
transition: background-color var(--transition-duration-normal) ease,
9382
transform var(--transition-duration-fast) var(--transition-easing);
94-
/* --- [动画] END --- */
9583
height: 72px; /* Fixed height for virtual scroller */
9684
border-bottom: 1px solid var(--color-border);
9785
position: relative; /* For the highlight bar */
9886
overflow: hidden; /* Hide the highlight bar initially */
9987
}
10088
101-
/* --- [动画] START: 新消息提醒动画 & 悬浮效果 --- */
10289
@keyframes highlight-bar {
10390
0% {
10491
transform: translateX(-100%);
@@ -136,10 +123,22 @@ watch(
136123
transform: translateY(-2px);
137124
box-shadow: var(--shadow-md);
138125
}
139-
/* --- [动画] END --- */
140126
141127
.chat-list-item.active { background-color: var(--color-background-active); }
142-
.chat-info { flex-grow: 1; margin-left: var(--spacing-3); overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
128+
129+
.chat-info {
130+
flex-grow: 1;
131+
margin-left: var(--spacing-3);
132+
overflow: hidden;
133+
display: flex;
134+
flex-direction: column;
135+
justify-content: center;
136+
/* ✅ MODIFICATION START: Add transition for smooth appearance */
137+
transition: opacity 0.1s ease-out, transform 0.2s ease-out;
138+
opacity: 1;
139+
transform: translateX(0);
140+
/* ✅ MODIFICATION END */
141+
}
143142
.info-top, .info-bottom { display: flex; justify-content: space-between; align-items: center; }
144143
.info-bottom { margin-top: 2px; }
145144
.name { font-weight: var(--font-weight-semibold); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; flex-grow: 1; }
@@ -148,4 +147,23 @@ watch(
148147
.timestamp { font-size: var(--font-size-xs); color: var(--color-text-secondary); flex-shrink: 0; margin-left: var(--spacing-2); }
149148
.preview { font-size: var(--font-size-sm); color: var(--color-text-secondary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; flex-grow: 1; line-height: 1.4; }
150149
.unread-badge { background-color: var(--color-brand-secondary); color: var(--color-text-on-brand); border-radius: var(--border-radius-pill); padding: 2px 8px; font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); min-width: 20px; text-align: center; flex-shrink: 0; margin-left: var(--spacing-2); }
150+
151+
/* --- ✅ MODIFICATION START: Styles for the collapsed sidebar state --- */
152+
/*
153+
Use `:deep()` to target the `.sidebar-collapsed` class which is applied on a parent component (`App.vue`).
154+
This allows us to style this component based on a global layout state.
155+
*/
156+
:deep(.sidebar-collapsed) .chat-list-item {
157+
justify-content: center; /* Center the avatar horizontally */
158+
padding: var(--spacing-2) 0; /* Adjust padding for a centered look */
159+
}
160+
161+
:deep(.sidebar-collapsed) .chat-list-item .chat-info {
162+
/* Instead of `display: none`, use opacity and transform for a smoother transition */
163+
opacity: 0;
164+
transform: translateX(-10px);
165+
pointer-events: none; /* Prevent interaction with hidden elements */
166+
position: absolute; /* Take it out of the layout flow */
167+
}
168+
/* --- ✅ MODIFICATION END --- */
151169
</style>

‎vue/src/components/Shared/AnimatedSticker.vue‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
</template>
44

55
<script setup>
6-
// --- ✅ MODIFICATION START ---
7-
import { ref, onMounted, onUnmounted, watch, defineExpose } from 'vue';
8-
// --- ✅ MODIFICATION END ---
6+
// ✅ MODIFICATION START: Removed defineExpose from import
7+
import { ref, onMounted, onUnmounted, watch } from 'vue';
8+
// ✅ MODIFICATION END
99
import lottie from 'lottie-web';
1010
import pako from 'pako';
1111
@@ -14,7 +14,6 @@ const props = defineProps({
1414
type: String,
1515
required: true,
1616
},
17-
// ✅ MODIFICATION START: Add loop prop
1817
loop: {
1918
type: Boolean,
2019
default: false,
@@ -43,10 +42,8 @@ const loadAnimation = async () => {
4342
animationInstance = lottie.loadAnimation({
4443
container: lottieContainer.value,
4544
renderer: 'svg',
46-
// ✅ MODIFICATION START: Use loop prop for loop and autoplay
4745
loop: props.loop,
4846
autoplay: props.loop,
49-
// ✅ MODIFICATION END
5047
animationData: animationData,
5148
});
5249
@@ -59,7 +56,7 @@ const loadAnimation = async () => {
5956
}
6057
};
6158
62-
// --- ✅ MODIFICATION START: Expose control methods to parent component ---
59+
// Expose control methods to the parent component
6360
defineExpose({
6461
/**
6562
* Plays the animation from the beginning.
@@ -80,7 +77,6 @@ defineExpose({
8077
}
8178
}
8279
});
83-
// --- ✅ MODIFICATION END ---
8480
8581
8682
onMounted(() => {

0 commit comments

Comments
 (0)