feat(stacked-cards): 调整卡片组样式

This commit is contained in:
肖应宇 2026-03-31 15:26:50 +08:00
parent 9b75000841
commit 64c441ba36
4 changed files with 62 additions and 55 deletions

View File

@ -68,11 +68,11 @@
<button v-if="isStreaming" class="action-btn stop" title="停止生成" @click="$emit('stop')">
<StopCircle :size="20" />
</button>
<button v-else class="action-btn send" :class="{ active: canSend, loading: isProcessingAttachments }" :disabled="!canSend"
:title="isProcessingAttachments ? '附件处理中...' : '发送消息 (Ctrl+Enter)'" @click="handleSend">
<Loader2 v-if="isProcessingAttachments" :size="20" class="animate-spin" />
<SendIcon v-else :size="20" />
</button>
<button v-else class="action-btn send" :class="{ active: canSend, loading: isProcessingAttachments }" :disabled="!canSend"
:title="isProcessingAttachments ? '附件处理中...' : '发送消息 (Ctrl+Enter)'" @click="handleSend">
<Loader2 v-if="isProcessingAttachments" :size="20" class="animate-spin" />
<SendIcon v-else :size="20" />
</button>
</div>
</div>
</div>
@ -97,11 +97,11 @@ import StackedCards from "@/components/ui/StackedCards.vue";
import PlusIcon from "../icons/custom/PlusIcon.vue";
import SendIcon from "../icons/custom/SendIcon.vue";
interface AttachmentWithProgress extends Attachment {
uploading?: boolean;
progress?: number;
deleting?: boolean;
}
interface AttachmentWithProgress extends Attachment {
uploading?: boolean;
progress?: number;
deleting?: boolean;
}
const props = withDefaults(
defineProps<{
@ -182,19 +182,19 @@ function showThrottledToast(message: string, type: "error" = "error") {
}
//
const charCount = computed(() => inputText.value.length);
const isUploading = computed(() => attachments.value.some((a) => a.uploading));
const isProcessingAttachments = computed(() =>
attachments.value.some((a) => a.uploading || a.deleting),
);
const canSend = computed(() => {
return (
(inputText.value.trim().length > 0 || attachments.value.length > 0) &&
!props.disabled &&
charCount.value <= props.maxChars &&
!isProcessingAttachments.value
);
});
const charCount = computed(() => inputText.value.length);
const isUploading = computed(() => attachments.value.some((a) => a.uploading));
const isProcessingAttachments = computed(() =>
attachments.value.some((a) => a.uploading || a.deleting),
);
const canSend = computed(() => {
return (
(inputText.value.trim().length > 0 || attachments.value.length > 0) &&
!props.disabled &&
charCount.value <= props.maxChars &&
!isProcessingAttachments.value
);
});
//
function autoResize() {
@ -382,28 +382,28 @@ async function uploadFileToServer(id: string, file: File) {
}
//
async function removeAttachment(id: string | number) {
const targetId = String(id);
const index = attachments.value.findIndex((a) => a.id === targetId);
if (index === -1) return;
const attachment = attachments.value[index];
let deletedFromOss = false;
// OSS blob URL OSS
if (attachment.url && !attachment.url.startsWith("blob:")) {
try {
attachment.deleting = true;
await nextTick();
await chatApi.deleteAttachment(attachment.url);
deletedFromOss = true;
} catch (error) {
console.error("删除 OSS 文件失败:", error);
// 使
} finally {
attachment.deleting = false;
}
}
async function removeAttachment(id: string | number) {
const targetId = String(id);
const index = attachments.value.findIndex((a) => a.id === targetId);
if (index === -1) return;
const attachment = attachments.value[index];
let deletedFromOss = false;
// OSS blob URL OSS
if (attachment.url && !attachment.url.startsWith("blob:")) {
try {
attachment.deleting = true;
await nextTick();
await chatApi.deleteAttachment(attachment.url);
deletedFromOss = true;
} catch (error) {
console.error("删除 OSS 文件失败:", error);
// 使
} finally {
attachment.deleting = false;
}
}
// blob URL
if (attachment.url.startsWith("blob:")) {
@ -718,8 +718,8 @@ onMounted(() => {
display: grid;
place-items: center;
border-radius: 0;
width: 88px;
height: 118px;
width: 50px;
height: 70px;
background-color: rgb(255, 255, 255);
}

View File

@ -625,6 +625,13 @@ onBeforeUnmount(() => {
}
}
.group-list{
gap:5px;
display:flex;
flex-direction:column;
padding: 0 20px;
}
.empty-state {
margin: auto 0;
display: flex;

View File

@ -175,7 +175,6 @@ function handleDelete() {
align-items: center;
gap: 10px;
padding: 10px 12px;
margin: 2px 8px;
border-radius: 10px;
cursor: pointer;

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { NModal, NImage } from 'naive-ui'
import PlusIcon from '../icons/custom/PlusIcon.vue'
export interface CardItem {
id: string | number
title?: string
@ -27,7 +28,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), {
maxVisible: 5,
spreadGap: 190,
spreadGap: 120,
supportsFiles: true,
supportsVision: true,
})
@ -318,20 +319,20 @@ onUnmounted(() => {
justify-content: center;
perspective: 1000px;
}
/* 卡片大小 */
.cards-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-width: 90px;
height: 130px;
justify-content: flex-end;
min-width: 70px;
height: 70px;
}
.card {
position: absolute;
width: 90px;
height: 120px;
width: 90%;
height: 100%;
border-radius: 5px;
border: 1px solid var(--card-border-color, var(--ffffff, #FFF));
background: url(<path-to-image>) lightgray 50% / cover no-repeat;