feat(upload-ui): 优化附件卡片交互与上传图标展示
This commit is contained in:
parent
88e43c1763
commit
135f3ae5c8
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M7 13C10.3137 13 13 10.3137 13 7C13 3.68629 10.3137 1 7 1C3.68629 1 1 3.68629 1 7"
|
||||
stroke="url(#paint0_linear_169_1705)"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_169_1705"
|
||||
x1="1"
|
||||
y1="7"
|
||||
x2="7"
|
||||
y2="13"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="white" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number;
|
||||
}>(),
|
||||
{
|
||||
size: 14,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13" fill="none">
|
||||
<path d="M0.5 6.49457H12.5" stroke="#999999" stroke-linecap="round" />
|
||||
<path d="M6.49414 12.5L6.49414 0.5" stroke="#999999" stroke-linecap="round" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11" fill="none">
|
||||
<path d="M0.5 5.49469H10.5" stroke="#999999" stroke-linecap="round" />
|
||||
<path d="M5.49512 10.5L5.49512 0.5" stroke="#999999" stroke-linecap="round" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
>
|
||||
<rect width="12" height="12" rx="2" fill="white" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
size?: number;
|
||||
}>(),
|
||||
{
|
||||
size: 12,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
|
@ -8,7 +8,14 @@
|
|||
@remove="removeAttachment" @add-upload="triggerUploadInput" />
|
||||
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<input ref="uploadInputRef" type="file" multiple hidden @change="handleUploadSelect" />
|
||||
<input
|
||||
ref="uploadInputRef"
|
||||
type="file"
|
||||
:accept="uploadAccept"
|
||||
multiple
|
||||
hidden
|
||||
@change="handleUploadSelect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 文本输入框 -->
|
||||
|
|
@ -58,36 +65,35 @@
|
|||
<!-- 右侧功能按钮 -->
|
||||
<div class="input-actions right">
|
||||
<!-- 发送/停止按钮 -->
|
||||
<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-if="isStreaming" class="action-btn stop" title="停止生成" @click="$emit('stop')">
|
||||
<StopIcon />
|
||||
</button>
|
||||
<button v-else class="action-btn send" :class="{ active: canSend, loading: isProcessingAttachments }"
|
||||
:disabled="!canSend" :title="isProcessingAttachments ? '附件处理中...' : '发送消息 (Ctrl+Enter)'" @click="handleSend">
|
||||
<LoadingIcon v-if="isProcessingAttachments" class="animate-spin" />
|
||||
<SendIcon v-else :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick, onMounted } from "vue";
|
||||
import {
|
||||
StopCircle,
|
||||
Sparkles,
|
||||
Globe,
|
||||
Brain,
|
||||
Loader2,
|
||||
Upload,
|
||||
} from "@/components/icons";
|
||||
import { ref, computed, watch, nextTick, onMounted } from "vue";
|
||||
import {
|
||||
Sparkles,
|
||||
Globe,
|
||||
Brain,
|
||||
} from "@/components/icons";
|
||||
import { generateId } from "@/utils/helpers";
|
||||
import type { Attachment } from "@/types/chat";
|
||||
import { chatApi } from "@/services/api";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import StackedCards from "@/components/ui/StackedCards.vue";
|
||||
import SendIcon from "../icons/custom/SendIcon.vue";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import StackedCards from "@/components/ui/StackedCards.vue";
|
||||
import SendIcon from "../icons/custom/SendIcon.vue";
|
||||
import StopIcon from "../icons/custom/StopIcon.vue";
|
||||
import LoadingIcon from "../icons/custom/LoadingIcon.vue";
|
||||
|
||||
interface AttachmentWithProgress extends Attachment {
|
||||
uploading?: boolean;
|
||||
|
|
@ -175,10 +181,44 @@ 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 isUploading = computed(() => attachments.value.some((a) => a.uploading));
|
||||
const isProcessingAttachments = computed(() =>
|
||||
attachments.value.some((a) => a.uploading || a.deleting),
|
||||
);
|
||||
const textFileAccept =
|
||||
".txt,.md,.markdown,.pdf,.doc,.docx,.rtf,.csv,.tsv,.json,.xml,.html,.htm,.yaml,.yml,.log,.ini,.conf,.sql,.js,.ts,.jsx,.tsx,.py,.java,.c,.cpp,.h,.hpp,.go,.rs,.sh";
|
||||
const uploadAccept = computed(() => {
|
||||
if (props.supports_vision && props.supports_files) {
|
||||
return `image/*,${textFileAccept}`;
|
||||
}
|
||||
if (props.supports_vision) {
|
||||
return "image/*";
|
||||
}
|
||||
if (props.supports_files) {
|
||||
return textFileAccept;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
function getFileExt(fileName: string) {
|
||||
const idx = fileName.lastIndexOf(".");
|
||||
if (idx === -1) return "";
|
||||
return fileName.slice(idx).toLowerCase();
|
||||
}
|
||||
|
||||
function getUploadTypeByModel(file: File): "image" | "file" | null {
|
||||
const isImage =
|
||||
file.type.startsWith("image/") ||
|
||||
[".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg", ".heic"].includes(
|
||||
getFileExt(file.name),
|
||||
);
|
||||
|
||||
if (isImage) {
|
||||
return props.supports_vision ? "image" : null;
|
||||
}
|
||||
|
||||
return props.supports_files ? "file" : null;
|
||||
}
|
||||
const canSend = computed(() => {
|
||||
return (
|
||||
(inputText.value.trim().length > 0 || attachments.value.length > 0) &&
|
||||
|
|
@ -261,7 +301,7 @@ function handleSend() {
|
|||
}
|
||||
|
||||
// 处理粘贴事件
|
||||
async function handlePaste(event: ClipboardEvent) {
|
||||
async function handlePaste(event: ClipboardEvent) {
|
||||
const items = event.clipboardData?.items;
|
||||
if (!items) return;
|
||||
|
||||
|
|
@ -281,31 +321,49 @@ async function handlePaste(event: ClipboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (item.type.startsWith("image/")) {
|
||||
event.preventDefault();
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
await addFileAsAttachment(file, "image");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function triggerUploadInput() {
|
||||
uploadInputRef.value?.click();
|
||||
}
|
||||
for (const item of items) {
|
||||
if (item.type.startsWith("image/")) {
|
||||
const file = item.getAsFile();
|
||||
if (file) {
|
||||
const uploadType = getUploadTypeByModel(file);
|
||||
if (uploadType === "image") {
|
||||
event.preventDefault();
|
||||
await addFileAsAttachment(file, uploadType);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
showThrottledToast("当前模型不支持上传图片");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function triggerUploadInput() {
|
||||
if (!props.supports_vision && !props.supports_files) {
|
||||
showThrottledToast("当前模型不支持上传附件");
|
||||
return;
|
||||
}
|
||||
uploadInputRef.value?.click();
|
||||
}
|
||||
|
||||
// 处理上传选择
|
||||
async function handleUploadSelect(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const files = input.files;
|
||||
if (!files) return;
|
||||
|
||||
for (const file of files) {
|
||||
const type = file.type.startsWith("image/") ? "image" : "file";
|
||||
await addFileAsAttachment(file, type);
|
||||
}
|
||||
if (!files) return;
|
||||
|
||||
for (const file of files) {
|
||||
const uploadType = getUploadTypeByModel(file);
|
||||
if (!uploadType) {
|
||||
showThrottledToast(
|
||||
file.type.startsWith("image/")
|
||||
? "当前模型不支持上传图片"
|
||||
: "当前模型不支持上传附件文件",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
await addFileAsAttachment(file, uploadType);
|
||||
}
|
||||
|
||||
input.value = "";
|
||||
}
|
||||
|
|
@ -568,7 +626,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
&.send {
|
||||
background: #e5e7eb;
|
||||
background: rgba(0, 15, 51, 0.20);
|
||||
color: #9ca3af;
|
||||
|
||||
.dark & {
|
||||
|
|
@ -586,26 +644,26 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
&.loading {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
background: #000F33;
|
||||
color: white;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: rgba(0, 15, 51, 0.20);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&.stop {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
color: white;
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
&.stop {
|
||||
background: #000F33;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-wrapper {
|
||||
|
|
@ -703,15 +761,4 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { NModal, NImage } from 'naive-ui'
|
||||
import { NModal, NImage, NTooltip } from 'naive-ui'
|
||||
import PlusIcon from '../icons/custom/PlusIcon.vue'
|
||||
export interface CardItem {
|
||||
id: string | number
|
||||
|
|
@ -262,42 +262,85 @@ watch(
|
|||
@click="expandCards">
|
||||
<div class="cards-wrapper" @mouseenter="isWrapperHovered = true" @mouseleave="isWrapperHovered = false">
|
||||
<TransitionGroup name="card-spread">
|
||||
<div v-for="(card, index) in cards" :key="card.id" class="card" :style="cardStyle(index, cards.length)"
|
||||
@mouseenter="hoveredCardId = card.id" @mouseleave="hoveredCardId = null" @click="handleCardClick(card)">
|
||||
<div class="card-glow" :style="{ background: getCardColor(card, index) }" />
|
||||
<div v-if="card.type === 'image' && getCardImageUrl(card)" class="card-media">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
<div v-if="card.uploading" class="card-media-uploading">
|
||||
<div class="card-media-uploading-spinner" aria-hidden="true" />
|
||||
<span class="card-media-uploading-text">上传中</span>
|
||||
<NTooltip
|
||||
v-for="(card, index) in cards"
|
||||
:key="card.id"
|
||||
trigger="hover"
|
||||
placement="top"
|
||||
>
|
||||
<template #trigger>
|
||||
<div class="card" :style="cardStyle(index, cards.length)"
|
||||
@mouseenter="hoveredCardId = card.id" @mouseleave="hoveredCardId = null" @click="handleCardClick(card)">
|
||||
<div class="card-glow" :style="{ background: getCardColor(card, index) }" />
|
||||
<div v-if="card.type === 'image' && getCardImageUrl(card)" class="card-media">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
<div v-if="card.uploading" class="card-media-uploading">
|
||||
<div class="card-media-uploading-spinner" aria-hidden="true" />
|
||||
<span class="card-media-uploading-text">上传中</span>
|
||||
</div>
|
||||
<div v-else-if="card.deleting" class="card-media-deleting">
|
||||
<div class="card-media-deleting-spinner" aria-hidden="true" />
|
||||
<span class="card-media-deleting-text">删除中</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="card.type === 'image'" class="card-preview">
|
||||
<div v-if="getCardImageUrl(card)" class="card-preview-image">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
</div>
|
||||
<div class="card-preview-fallback">
|
||||
<span class="card-icon">
|
||||
{{ getCardIcon(card) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="card.deleting" class="card-preview-deleting">
|
||||
<div class="card-preview-deleting-spinner" aria-hidden="true" />
|
||||
<span class="card-preview-deleting-text">删除中</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="card-file-preview">
|
||||
<div class="card-file-icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<rect x="2.5" y="1.5" width="11" height="13" rx="1.5" stroke="#666666" />
|
||||
<path d="M5 5H11" stroke="#666666" stroke-linecap="round" />
|
||||
<path d="M5 8H11" stroke="#666666" stroke-linecap="round" />
|
||||
<path d="M5 11H11" stroke="#666666" stroke-linecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-file-name" :title="getCardTitle(card, index)">
|
||||
{{ getCardTitle(card, index) }}
|
||||
</div>
|
||||
<div v-if="card.deleting" class="card-preview-deleting">
|
||||
<div class="card-preview-deleting-spinner" aria-hidden="true" />
|
||||
<span class="card-preview-deleting-text">删除中</span>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!card.deleting && isWrapperHovered && (hoveredCardId === card.id || (!hoveredCardId && index === 0))"
|
||||
type="button" class="card-delete-btn" title="删除 OSS 文件" @click.stop="removeCard(card)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<rect width="16.0008" height="16" rx="8" fill="#999999" />
|
||||
<path d="M5.49512 5.33374L10.6692 10.6624" stroke="white" stroke-linecap="round" />
|
||||
<path d="M10.5068 5.33813L5.33271 10.6668" stroke="white" stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="card.deleting" class="card-media-deleting">
|
||||
<div class="card-media-deleting-spinner" aria-hidden="true" />
|
||||
<span class="card-media-deleting-text">删除中</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="card-preview">
|
||||
<div v-if="getCardImageUrl(card)" class="card-preview-image">
|
||||
<img :src="getCardImageUrl(card)" :alt="getCardTitle(card, index)" />
|
||||
</div>
|
||||
<div class="card-preview-fallback">
|
||||
<span class="card-icon">
|
||||
{{ getCardIcon(card) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="card.deleting" class="card-preview-deleting">
|
||||
<div class="card-preview-deleting-spinner" aria-hidden="true" />
|
||||
<span class="card-preview-deleting-text">删除中</span>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!card.deleting && isWrapperHovered && (hoveredCardId === card.id || (!hoveredCardId && index === 0))"
|
||||
type="button" class="card-delete-btn" title="删除 OSS 文件" @click.stop="removeCard(card)">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
{{ getCardTitle(card, index) }}
|
||||
</NTooltip>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="!isExpanded"
|
||||
type="button"
|
||||
class="cards-upload-fab"
|
||||
:class="{ disabled: !canUpload }"
|
||||
:disabled="!canUpload"
|
||||
:title="canUpload ? '上传附件或图片' : '当前模型不支持上传'"
|
||||
@click.stop="emit('add-upload')"
|
||||
>
|
||||
<PlusIcon :size="12" />
|
||||
</button>
|
||||
|
||||
<NModal v-model:show="isPreviewVisible" preset="card" :mask-closable="true" :close-on-esc="true"
|
||||
:on-close="removePreviewCard" class="image-preview-modal"
|
||||
:content-style="{ width: '100%', height: '100%', padding: '0', overflow: 'hidden' }"
|
||||
|
|
@ -329,6 +372,35 @@ watch(
|
|||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.cards-upload-fab {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
z-index: 999;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
color: #999999;
|
||||
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.16);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, opacity 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.cards-upload-fab:hover:not(:disabled) {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.cards-upload-fab.disabled,
|
||||
.cards-upload-fab:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.stacked-cards-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -358,7 +430,8 @@ watch(
|
|||
}
|
||||
|
||||
.card-media,
|
||||
.card-preview {
|
||||
.card-preview,
|
||||
.card-file-preview {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
|
|
@ -456,29 +529,31 @@ watch(
|
|||
|
||||
.card-delete-btn {
|
||||
position: absolute;
|
||||
top: 0.35rem;
|
||||
right: 0.35rem;
|
||||
top: 0.3rem;
|
||||
right: 0.3rem;
|
||||
z-index: 4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 0.95rem;
|
||||
height: 0.95rem;
|
||||
width: 16.001px;
|
||||
height: 16px;
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
color: #fff;
|
||||
font-size: 0.55rem;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: transform 0.2s ease, opacity 0.2s ease, background 0.2s ease;
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.card-delete-btn:hover {
|
||||
transform: scale(1.08);
|
||||
background: rgba(220, 38, 38, 0.98);
|
||||
}
|
||||
|
||||
.card-delete-btn svg {
|
||||
width: 16.001px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-media img,
|
||||
|
|
@ -555,6 +630,38 @@ watch(
|
|||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.card-file-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 6px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.card-file-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-file-name {
|
||||
width: 100%;
|
||||
color: var(--6-666666, #666);
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.card:hover .card-glow {
|
||||
opacity: 0.15;
|
||||
|
|
|
|||
Loading…
Reference in New Issue