feat(share): 分享对话修改为单个对话分享
This commit is contained in:
parent
0eb226ff60
commit
3f9cfc80b0
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue