feat(share): 分享对话修改为单个对话分享

This commit is contained in:
肖应宇 2026-03-27 12:40:45 +08:00
parent 0eb226ff60
commit 3f9cfc80b0
7 changed files with 72 additions and 65 deletions

View File

@ -34,18 +34,14 @@
<!-- 对话分享预览 -->
<div v-else class="selected-preview">
<div class="preview-header">
<span class="preview-title">已选择 {{ selectedCount }} 对话</span>
<span class="preview-hint">最多分享 10 个对话</span>
<span class="preview-title">当前对话</span>
<span class="preview-hint">仅支持单个对话分享</span>
</div>
<div class="preview-list">
<div
v-for="conv in selectedConversations"
:key="conv.id"
class="preview-item"
>
<div v-if="shareConversation" class="preview-list">
<div class="preview-item">
<MessageSquare :size="14" />
<span class="item-title">{{ conv.title }}</span>
<span class="item-count">{{ conv.messages.length }} 条消息</span>
<span class="item-title">{{ shareConversation.title }}</span>
<span class="item-count">{{ shareConversation.messages.length }} 条消息</span>
</div>
</div>
</div>
@ -120,10 +116,15 @@ const settingsStore = useSettingsStore();
const chatStore = useChatStore();
const show = computed(() => settingsStore.showShareModal);
const { selectedConversations, selectedCount, isMessageSelectMode, selectedMessages, selectedMessageCount } = storeToRefs(chatStore);
const { shareConversationId } = storeToRefs(settingsStore);
const { isMessageSelectMode, selectedMessages, selectedMessageCount } = storeToRefs(chatStore);
//
const isMessageShare = computed(() => isMessageSelectMode.value);
const shareConversation = computed(() => {
if (!shareConversationId.value) return null;
return chatStore.conversations.find((c) => c.id === shareConversationId.value) || null;
});
const password = ref("");
const showPassword = ref(false);
@ -173,16 +174,13 @@ async function handleCreateShare() {
});
} else {
//
//
if (selectedCount.value > SHARE_LIMITS.MAX_CONVERSATIONS) {
window.$toast?.(`最多分享 ${SHARE_LIMITS.MAX_CONVERSATIONS} 个对话`, 'error');
if (!shareConversation.value) {
window.$toast?.('请先选择要分享的对话', 'error');
return;
}
const conversationIds = selectedConversations.value.map(c => c.id);
result = await shareApi.createShare({
conversationIds,
conversationIds: [shareConversation.value.id],
passwordHash,
expiresIn: SHARE_LIMITS.DEFAULT_EXPIRE_SECONDS,
});
@ -207,8 +205,6 @@ async function handleCreateShare() {
password.value = "";
if (isMessageShare.value) {
chatStore.exitMessageSelectMode();
} else {
chatStore.exitSelectMode();
}
} catch (error) {

View File

@ -76,7 +76,7 @@
<ConversationItem v-for="conv in pinnedConversations" :key="conv.id" :conversation="conv"
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
@rename="renameConversation" @toggle-pin="togglePinConversation"
@rename="renameConversation" @toggle-pin="togglePinConversation" @share="handleShareConversation"
@toggle-select="toggleConversationSelection" />
</div>
</div>
@ -91,7 +91,7 @@
<ConversationItem v-for="conv in recentConversations" :key="conv.id" :conversation="conv"
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
@rename="renameConversation" @toggle-pin="togglePinConversation"
@rename="renameConversation" @toggle-pin="togglePinConversation" @share="handleShareConversation"
@toggle-select="toggleConversationSelection" />
</div>
</div>
@ -210,6 +210,10 @@ function togglePinConversation(id: string) {
chatStore.togglePinConversation(id);
}
function handleShareConversation(id: string) {
settingsStore.openConversationShareModal(id);
}
function toggleConversationSelection(id: string) {
chatStore.toggleConversationSelection(id);
}

View File

@ -52,7 +52,7 @@
<n-tooltip :style="{ borderRadius: '5px', padding: '7px 15px' }">
<template #trigger>
<button class="action-btn" @click="handleDelete">
<button class="action-btn" @click="handleShare">
<ShareIcon :size="14" />
</button>
</template>
@ -108,6 +108,7 @@ const emit = defineEmits<{
rename: [id: string, title: string];
togglePin: [id: string];
toggleSelect: [id: string];
share: [id: string];
}>();
const isEditing = ref(false);
@ -134,6 +135,10 @@ function handleTogglePin() {
emit("togglePin", props.conversation.id);
}
function handleShare() {
emit("share", props.conversation.id);
}
function handleRename() {
if (props.isSelectMode) return;
isEditing.value = true;
@ -201,6 +206,11 @@ function handleDelete() {
.item-title {
font-weight: 700;
}
.item-actions {
opacity: 1;
pointer-events: auto;
}
}
}
@ -278,6 +288,10 @@ function handleDelete() {
transition: opacity 0.2s ease;
}
.conversation-item.active .pin-indicator {
opacity: 0;
}
.action-btn {
display: flex;
align-items: center;

View File

@ -1,32 +1,9 @@
<template>
<div class="share-button-wrapper">
<button
v-if="!isSelectMode"
class="share-btn"
:disabled="conversations.length === 0"
@click="handleStartSelect"
>
<button class="share-btn" :disabled="!currentConversation" @click="handleShareCurrent">
<Share2 :size="16" />
<span>分享对话</span>
</button>
<div v-else class="select-actions">
<span class="select-info">
已选择 {{ selectedCount }} 个对话
</span>
<div class="action-buttons">
<button class="action-btn cancel" @click="handleCancel">
取消
</button>
<button
class="action-btn confirm"
:disabled="selectedCount === 0"
@click="handleConfirm"
>
确认分享
</button>
</div>
</div>
</div>
</template>
@ -39,21 +16,12 @@ import { Share2 } from "@/components/icons";
const chatStore = useChatStore();
const settingsStore = useSettingsStore();
const { isSelectMode, selectedCount, conversations } = storeToRefs(chatStore);
const { currentConversation } = storeToRefs(chatStore);
function handleStartSelect() {
chatStore.enterSelectMode();
}
function handleCancel() {
chatStore.exitSelectMode();
}
function handleConfirm() {
if (chatStore.selectedCount > 0) {
//
settingsStore.openShareModal();
}
function handleShareCurrent() {
const conversation = currentConversation.value;
if (!conversation) return;
settingsStore.openConversationShareModal(conversation.id);
}
</script>
@ -162,4 +130,4 @@ function handleConfirm() {
}
}
}
</style>
</style>

View File

@ -13,6 +13,7 @@ const router = createRouter({
path: '/share/:id',
name: 'share',
component: () => import('@/views/ShareView.vue'),
alias: ['/chat-ui/share/:id'],
},
{
path: '/demo/cards',
@ -22,4 +23,4 @@ const router = createRouter({
],
})
export default router
export default router

View File

@ -101,6 +101,7 @@ export const useSettingsStore = defineStore("settings", () => {
const showShareModal = ref(false);
const showShareResultModal = ref(false);
const shareResult = ref<ShareResult | null>(null);
const shareConversationId = ref<string | null>(null);
// 主题相关
function applyTheme(theme: AppSettings["theme"]) {
@ -196,8 +197,14 @@ export const useSettingsStore = defineStore("settings", () => {
showShareModal.value = true;
}
function openConversationShareModal(conversationId: string) {
shareConversationId.value = conversationId;
showShareModal.value = true;
}
function closeShareModal() {
showShareModal.value = false;
shareConversationId.value = null;
}
function openShareResultModal() {
@ -343,6 +350,7 @@ export const useSettingsStore = defineStore("settings", () => {
showShareModal,
showShareResultModal,
shareResult,
shareConversationId,
// 方法
toggleTheme,
@ -360,6 +368,7 @@ export const useSettingsStore = defineStore("settings", () => {
closeConversationSettingsModal,
// 分享模态框方法
openShareModal,
openConversationShareModal,
closeShareModal,
openShareResultModal,
closeShareResultModal,

View File

@ -10,21 +10,36 @@ export function formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
const absDiff = Math.abs(diff);
if (diff < 60 * 1000) {
if (diff >= 0 && diff < 60 * 1000) {
return "刚刚";
}
if (diff < 60 * 60 * 1000) {
if (diff < 0 && absDiff < 60 * 1000) {
return "即将";
}
if (diff >= 0 && diff < 60 * 60 * 1000) {
const minutes = Math.floor(diff / (60 * 1000));
return `${minutes}分钟前`;
}
if (diff < 24 * 60 * 60 * 1000) {
if (diff < 0 && absDiff < 60 * 60 * 1000) {
const minutes = Math.floor(absDiff / (60 * 1000));
return `${minutes}分钟后`;
}
if (diff >= 0 && diff < 24 * 60 * 60 * 1000) {
const hours = Math.floor(diff / (60 * 60 * 1000));
return `${hours}小时前`;
}
if (diff < 0 && absDiff < 24 * 60 * 60 * 1000) {
const hours = Math.floor(absDiff / (60 * 60 * 1000));
return `${hours}小时后`;
}
if (date.getFullYear() === now.getFullYear()) {
return `${date.getMonth() + 1}${date.getDate()}${padZero(date.getHours())}:${padZero(date.getMinutes())}`;
}