ai-chat-ui/src/components/input/UploadActionCards.vue

195 lines
4.0 KiB
Vue

<script setup lang="ts">
import { computed } from "vue";
type UploadAction = "file" | "image";
interface ActionCard {
id: UploadAction;
title: string;
description: string;
icon: string;
color: string;
disabled: boolean;
}
const props = withDefaults(
defineProps<{
supportsFiles?: boolean;
supportsVision?: boolean;
}>(),
{
supportsFiles: true,
supportsVision: true,
},
);
const emit = defineEmits<{
file: [];
image: [];
}>();
const cards = computed<ActionCard[]>(() => [
{
id: "file",
title: "附件",
description: props.supportsFiles ? "上传文档 / 压缩包" : "当前模型不支持附件",
icon: "📎",
color: "#06b6d4",
disabled: !props.supportsFiles,
},
{
id: "image",
title: "图片",
description: props.supportsVision ? "上传图片 / 截图" : "当前模型不支持图片",
icon: "🖼️",
color: "#8b5cf6",
disabled: !props.supportsVision,
},
]);
function handleClick(action: UploadAction) {
if (action === "file" && props.supportsFiles) {
emit("file");
}
if (action === "image" && props.supportsVision) {
emit("image");
}
}
</script>
<template>
<div class="upload-card-group" aria-label="上传入口">
<button
type="button"
v-for="(card, index) in cards"
:key="card.id"
class="upload-card"
:class="{ disabled: card.disabled, [`card-${card.id}`]: true }"
:style="{
'--card-color': card.color,
'--card-border': `${card.color}33`,
'--card-glow': `${card.color}40`,
'--card-glow-fade': `${card.color}14`,
'--card-offset': `${index * 14}px`,
zIndex: cards.length - index,
}"
:disabled="card.disabled"
:title="card.description"
@click="handleClick(card.id)"
>
<span class="card-glow" />
<span class="card-icon">{{ card.icon }}</span>
<span class="card-copy">
<span class="card-title">{{ card.title }}</span>
<span class="card-desc">{{ card.description }}</span>
</span>
</button>
</div>
</template>
<style scoped lang="scss">
.upload-card-group {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
min-width: 164px;
height: 44px;
}
.upload-card {
position: absolute;
left: var(--card-offset);
top: 0;
display: flex;
align-items: center;
gap: 10px;
width: 138px;
height: 44px;
padding: 0 12px;
border: 1px solid var(--card-border);
border-radius: 999px;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(245, 247, 250, 0.96));
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.08);
color: #1f2937;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease, border-color 0.2s ease;
overflow: hidden;
}
.upload-card:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 14px 24px rgba(15, 23, 42, 0.12);
}
.upload-card.disabled,
.upload-card:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.card-glow {
position: absolute;
inset: -20%;
background: radial-gradient(circle at left center, var(--card-glow), transparent 58%);
opacity: 0.45;
pointer-events: none;
}
.card-icon {
position: relative;
z-index: 1;
flex-shrink: 0;
font-size: 18px;
}
.card-copy {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
min-width: 0;
text-align: left;
}
.card-title {
font-size: 14px;
font-weight: 700;
line-height: 1.1;
}
.card-desc {
margin-top: 2px;
font-size: 11px;
line-height: 1.2;
color: #6b7280;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dark {
.upload-card {
background: linear-gradient(135deg, rgba(30, 30, 46, 0.98), rgba(24, 24, 37, 0.98));
color: #f3f4f6;
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22);
}
.card-desc {
color: #9ca3af;
}
}
@media (max-width: 640px) {
.upload-card-group {
min-width: 0;
height: 40px;
}
.upload-card {
width: 126px;
height: 40px;
}
}
</style>