feat(share): 分享对话修改为单个对话分享
This commit is contained in:
parent
0eb226ff60
commit
3f9cfc80b0
|
|
@ -34,18 +34,14 @@
|
||||||
<!-- 对话分享预览 -->
|
<!-- 对话分享预览 -->
|
||||||
<div v-else class="selected-preview">
|
<div v-else class="selected-preview">
|
||||||
<div class="preview-header">
|
<div class="preview-header">
|
||||||
<span class="preview-title">已选择 {{ selectedCount }} 个对话</span>
|
<span class="preview-title">当前对话</span>
|
||||||
<span class="preview-hint">最多分享 10 个对话</span>
|
<span class="preview-hint">仅支持单个对话分享</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-list">
|
<div v-if="shareConversation" class="preview-list">
|
||||||
<div
|
<div class="preview-item">
|
||||||
v-for="conv in selectedConversations"
|
|
||||||
:key="conv.id"
|
|
||||||
class="preview-item"
|
|
||||||
>
|
|
||||||
<MessageSquare :size="14" />
|
<MessageSquare :size="14" />
|
||||||
<span class="item-title">{{ conv.title }}</span>
|
<span class="item-title">{{ shareConversation.title }}</span>
|
||||||
<span class="item-count">{{ conv.messages.length }} 条消息</span>
|
<span class="item-count">{{ shareConversation.messages.length }} 条消息</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -120,10 +116,15 @@ const settingsStore = useSettingsStore();
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
const show = computed(() => settingsStore.showShareModal);
|
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 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 password = ref("");
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
|
|
@ -173,16 +174,13 @@ async function handleCreateShare() {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 对话分享模式
|
// 对话分享模式
|
||||||
// 检查数量限制
|
if (!shareConversation.value) {
|
||||||
if (selectedCount.value > SHARE_LIMITS.MAX_CONVERSATIONS) {
|
window.$toast?.('请先选择要分享的对话', 'error');
|
||||||
window.$toast?.(`最多分享 ${SHARE_LIMITS.MAX_CONVERSATIONS} 个对话`, 'error');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationIds = selectedConversations.value.map(c => c.id);
|
|
||||||
|
|
||||||
result = await shareApi.createShare({
|
result = await shareApi.createShare({
|
||||||
conversationIds,
|
conversationIds: [shareConversation.value.id],
|
||||||
passwordHash,
|
passwordHash,
|
||||||
expiresIn: SHARE_LIMITS.DEFAULT_EXPIRE_SECONDS,
|
expiresIn: SHARE_LIMITS.DEFAULT_EXPIRE_SECONDS,
|
||||||
});
|
});
|
||||||
|
|
@ -207,8 +205,6 @@ async function handleCreateShare() {
|
||||||
password.value = "";
|
password.value = "";
|
||||||
if (isMessageShare.value) {
|
if (isMessageShare.value) {
|
||||||
chatStore.exitMessageSelectMode();
|
chatStore.exitMessageSelectMode();
|
||||||
} else {
|
|
||||||
chatStore.exitSelectMode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
<ConversationItem v-for="conv in pinnedConversations" :key="conv.id" :conversation="conv"
|
<ConversationItem v-for="conv in pinnedConversations" :key="conv.id" :conversation="conv"
|
||||||
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
|
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
|
||||||
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
|
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
|
||||||
@rename="renameConversation" @toggle-pin="togglePinConversation"
|
@rename="renameConversation" @toggle-pin="togglePinConversation" @share="handleShareConversation"
|
||||||
@toggle-select="toggleConversationSelection" />
|
@toggle-select="toggleConversationSelection" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
<ConversationItem v-for="conv in recentConversations" :key="conv.id" :conversation="conv"
|
<ConversationItem v-for="conv in recentConversations" :key="conv.id" :conversation="conv"
|
||||||
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
|
:is-active="conv.id === currentConversationId" :is-select-mode="isSelectMode"
|
||||||
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
|
:is-selected="isConversationSelected(conv.id)" @select="selectConversation" @delete="deleteConversation"
|
||||||
@rename="renameConversation" @toggle-pin="togglePinConversation"
|
@rename="renameConversation" @toggle-pin="togglePinConversation" @share="handleShareConversation"
|
||||||
@toggle-select="toggleConversationSelection" />
|
@toggle-select="toggleConversationSelection" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -210,6 +210,10 @@ function togglePinConversation(id: string) {
|
||||||
chatStore.togglePinConversation(id);
|
chatStore.togglePinConversation(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleShareConversation(id: string) {
|
||||||
|
settingsStore.openConversationShareModal(id);
|
||||||
|
}
|
||||||
|
|
||||||
function toggleConversationSelection(id: string) {
|
function toggleConversationSelection(id: string) {
|
||||||
chatStore.toggleConversationSelection(id);
|
chatStore.toggleConversationSelection(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
<n-tooltip :style="{ borderRadius: '5px', padding: '7px 15px' }">
|
<n-tooltip :style="{ borderRadius: '5px', padding: '7px 15px' }">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<button class="action-btn" @click="handleDelete">
|
<button class="action-btn" @click="handleShare">
|
||||||
<ShareIcon :size="14" />
|
<ShareIcon :size="14" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -108,6 +108,7 @@ const emit = defineEmits<{
|
||||||
rename: [id: string, title: string];
|
rename: [id: string, title: string];
|
||||||
togglePin: [id: string];
|
togglePin: [id: string];
|
||||||
toggleSelect: [id: string];
|
toggleSelect: [id: string];
|
||||||
|
share: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isEditing = ref(false);
|
const isEditing = ref(false);
|
||||||
|
|
@ -134,6 +135,10 @@ function handleTogglePin() {
|
||||||
emit("togglePin", props.conversation.id);
|
emit("togglePin", props.conversation.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleShare() {
|
||||||
|
emit("share", props.conversation.id);
|
||||||
|
}
|
||||||
|
|
||||||
function handleRename() {
|
function handleRename() {
|
||||||
if (props.isSelectMode) return;
|
if (props.isSelectMode) return;
|
||||||
isEditing.value = true;
|
isEditing.value = true;
|
||||||
|
|
@ -201,6 +206,11 @@ function handleDelete() {
|
||||||
.item-title {
|
.item-title {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-actions {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +288,10 @@ function handleDelete() {
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation-item.active .pin-indicator {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="share-button-wrapper">
|
<div class="share-button-wrapper">
|
||||||
<button
|
<button class="share-btn" :disabled="!currentConversation" @click="handleShareCurrent">
|
||||||
v-if="!isSelectMode"
|
|
||||||
class="share-btn"
|
|
||||||
:disabled="conversations.length === 0"
|
|
||||||
@click="handleStartSelect"
|
|
||||||
>
|
|
||||||
<Share2 :size="16" />
|
<Share2 :size="16" />
|
||||||
<span>分享对话</span>
|
<span>分享对话</span>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -39,21 +16,12 @@ import { Share2 } from "@/components/icons";
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const { isSelectMode, selectedCount, conversations } = storeToRefs(chatStore);
|
const { currentConversation } = storeToRefs(chatStore);
|
||||||
|
|
||||||
function handleStartSelect() {
|
function handleShareCurrent() {
|
||||||
chatStore.enterSelectMode();
|
const conversation = currentConversation.value;
|
||||||
}
|
if (!conversation) return;
|
||||||
|
settingsStore.openConversationShareModal(conversation.id);
|
||||||
function handleCancel() {
|
|
||||||
chatStore.exitSelectMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleConfirm() {
|
|
||||||
if (chatStore.selectedCount > 0) {
|
|
||||||
// 打开分享设置模态框
|
|
||||||
settingsStore.openShareModal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -162,4 +130,4 @@ function handleConfirm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const router = createRouter({
|
||||||
path: '/share/:id',
|
path: '/share/:id',
|
||||||
name: 'share',
|
name: 'share',
|
||||||
component: () => import('@/views/ShareView.vue'),
|
component: () => import('@/views/ShareView.vue'),
|
||||||
|
alias: ['/chat-ui/share/:id'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/demo/cards',
|
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 showShareModal = ref(false);
|
||||||
const showShareResultModal = ref(false);
|
const showShareResultModal = ref(false);
|
||||||
const shareResult = ref<ShareResult | null>(null);
|
const shareResult = ref<ShareResult | null>(null);
|
||||||
|
const shareConversationId = ref<string | null>(null);
|
||||||
|
|
||||||
// 主题相关
|
// 主题相关
|
||||||
function applyTheme(theme: AppSettings["theme"]) {
|
function applyTheme(theme: AppSettings["theme"]) {
|
||||||
|
|
@ -196,8 +197,14 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||||
showShareModal.value = true;
|
showShareModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openConversationShareModal(conversationId: string) {
|
||||||
|
shareConversationId.value = conversationId;
|
||||||
|
showShareModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
function closeShareModal() {
|
function closeShareModal() {
|
||||||
showShareModal.value = false;
|
showShareModal.value = false;
|
||||||
|
shareConversationId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openShareResultModal() {
|
function openShareResultModal() {
|
||||||
|
|
@ -343,6 +350,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||||
showShareModal,
|
showShareModal,
|
||||||
showShareResultModal,
|
showShareResultModal,
|
||||||
shareResult,
|
shareResult,
|
||||||
|
shareConversationId,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
toggleTheme,
|
toggleTheme,
|
||||||
|
|
@ -360,6 +368,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||||
closeConversationSettingsModal,
|
closeConversationSettingsModal,
|
||||||
// 分享模态框方法
|
// 分享模态框方法
|
||||||
openShareModal,
|
openShareModal,
|
||||||
|
openConversationShareModal,
|
||||||
closeShareModal,
|
closeShareModal,
|
||||||
openShareResultModal,
|
openShareResultModal,
|
||||||
closeShareResultModal,
|
closeShareResultModal,
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,36 @@ export function formatTimestamp(timestamp: number): string {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diff = now.getTime() - date.getTime();
|
const diff = now.getTime() - date.getTime();
|
||||||
|
const absDiff = Math.abs(diff);
|
||||||
|
|
||||||
if (diff < 60 * 1000) {
|
if (diff >= 0 && diff < 60 * 1000) {
|
||||||
return "刚刚";
|
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));
|
const minutes = Math.floor(diff / (60 * 1000));
|
||||||
return `${minutes}分钟前`;
|
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));
|
const hours = Math.floor(diff / (60 * 60 * 1000));
|
||||||
return `${hours}小时前`;
|
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()) {
|
if (date.getFullYear() === now.getFullYear()) {
|
||||||
return `${date.getMonth() + 1}月${date.getDate()}日 ${padZero(date.getHours())}:${padZero(date.getMinutes())}`;
|
return `${date.getMonth() + 1}月${date.getDate()}日 ${padZero(date.getHours())}:${padZero(date.getMinutes())}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue