CodexUserAttachedFileCard.vue 4.05 KB
<template>
  <div
    class="file-generated-card codex-user-attached-card"
    role="button"
    tabindex="0"
    @click="onOpen"
    @keydown.enter.prevent="onOpen"
  >
    <div class="file-card-content">
      <div class="file-card-icon">
        <img :src="iconSrc" :alt="extension" @error="onImgError" />
      </div>
      <div class="file-card-info">
        <div class="file-card-name">{{ name }}</div>
        <div class="file-card-time">{{ kindLabel }}</div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
  getAttachmentKindLabel,
  getFileTypeFromNameForOpen,
} from "@/utils/attachmentFileMeta";
import { openWorkspaceAttachment } from "@/utils/openWorkspaceAttachment";

const props = defineProps<{
  id: number;
  name: string;
  filePath?: string;
  /** 后端或拖拽带来的类型提示,打开时仍以文件名为准推断 */
  type?: string;
}>();

const router = useRouter();
const route = useRoute();

const extension = computed(
  () => props.name.split(".").pop()?.toLowerCase() || "",
);

const iconSrc = computed(() => {
  const ext = extension.value;
  const iconMap: Record<string, string> = {
    md: "/md_icon.svg",
    pdf: "/pdf_icon.svg",
    doc: "/word_icon.svg",
    docx: "/word_icon.svg",
    xls: "/excel_icon.svg",
    xlsx: "/excel_icon.svg",
    ppt: "/PPT_icon.svg",
    pptx: "/PPT_icon.svg",
    txt: "/TXT_icon.svg",
    jpg: "/jpg_icon.svg",
    jpeg: "/jpg_icon.svg",
    png: "/png_icon.svg",
    gif: "/gif_icon.svg",
  };
  return iconMap[ext] || "/TXT_icon.svg";
});

const kindLabel = computed(() => getAttachmentKindLabel(props.name));

function onImgError(e: Event) {
  (e.target as HTMLImageElement).src = "/TXT_icon.svg";
}

async function onOpen() {
  await openWorkspaceAttachment(router, route, {
    fileName: props.name,
    filePath: props.filePath,
    fileType: getFileTypeFromNameForOpen(props.name),
    knowledgeFileId: props.filePath ? undefined : props.id,
  });
}
</script>

<style scoped>
/* 对齐 MarkdownPreview 中 .file-generated-card 的视觉 */
.file-generated-card {
  margin: 0 0 8px 0;
  max-width: 320px;
  padding: 12px 16px;
  background: var(--color-card, #2a2a2a);
  border: 1px solid var(--color-border, #444);
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s ease;
  display: block;
}

/* 悬停时保持与默认相同的卡片底色,避免 --color-card-hover 在部分主题下异常变黑 */
.file-generated-card.codex-user-attached-card:hover {
  background: var(--color-card, #2a2a2a);
  border-color: var(--color-primary, #409eff);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
}

.file-card-content {
  display: flex;
  align-items: center;
  gap: 12px;
}

.file-card-icon {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 6px;
}

.file-card-icon img {
  width: 32px;
  height: 32px;
  object-fit: contain;
}

.file-card-info {
  flex: 1;
  min-width: 0;
}

.file-card-name {
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text, #fff);
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-card-time {
  font-size: 12px;
  color: var(--color-text-secondary, #999);
}

html.light .file-generated-card,
:global(html.light) .file-generated-card {
  background: var(--color-card, #ffffff);
  border-color: var(--color-border, #e0e0e0);
}

html.light .file-generated-card.codex-user-attached-card:hover,
:global(html.light) .file-generated-card.codex-user-attached-card:hover {
  background: var(--color-card, #ffffff);
  border-color: var(--color-primary, #1e70ff);
}

html.light .file-card-icon,
:global(html.light) .file-card-icon {
  background: rgba(0, 0, 0, 0.05);
}

html.light .file-card-name,
:global(html.light) .file-card-name {
  color: var(--color-text, #24292e);
}

html.light .file-card-time,
:global(html.light) .file-card-time {
  color: var(--color-text-secondary, #666);
}
</style>