fix: FilePreview fileType case + Tailwind v4 gradient transparent bug
- FilePreview.vue: add normalizedFileType computed to handle backend returning uppercase HTML/MD/PPTX (fixes preview/download buttons) - FilePreview.vue: bg-gradient-to-r from-orange-500 -> bg-orange-500 (Tailwind v4 gradient + CSS variable = transparent) - ReportCard.vue: bg-gradient-to-r -> bg-orange-600 for selected state - Add .opencode/, node_modules/, dist/ to .gitignore - Initial git setup for publish project
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div
|
||||
@click="$emit('select', report)"
|
||||
:class="[
|
||||
'group relative rounded-xl cursor-pointer transition-all duration-300 overflow-hidden',
|
||||
isSelected
|
||||
? 'bg-orange-600 shadow-xl shadow-orange-600/40 border-2 border-orange-500'
|
||||
: 'glass border border-orange-200/50 hover:border-orange-400 hover:shadow-lg hover:-translate-y-0.5'
|
||||
]"
|
||||
>
|
||||
<div :class="['p-4', isSelected ? 'text-white' : 'text-slate-700']">
|
||||
<!-- File icon and info -->
|
||||
<div class="flex items-start space-x-3">
|
||||
<div :class="[
|
||||
'flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center shadow-lg transition-colors',
|
||||
isSelected ? 'bg-white/30' : iconClass
|
||||
]">
|
||||
<svg v-if="isSelected" class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<component v-else :is="fileIconComponent" class="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3
|
||||
:class="[
|
||||
'text-base font-semibold truncate select-none',
|
||||
isSelected ? 'text-white' : 'text-slate-800 group-hover:text-orange-600'
|
||||
]"
|
||||
:title="report.fileName"
|
||||
>
|
||||
{{ report.fileName }}
|
||||
</h3>
|
||||
<div class="mt-2 flex items-center space-x-3">
|
||||
<span :class="[
|
||||
'px-3 py-1 rounded-full text-xs font-semibold',
|
||||
isSelected ? 'bg-white/20 text-white' : typeBadgeClass
|
||||
]">
|
||||
{{ fileTypeLabel }}
|
||||
</span>
|
||||
<span :class="[
|
||||
'text-sm',
|
||||
isSelected ? 'text-white/80' : 'text-slate-500'
|
||||
]">
|
||||
{{ report.size }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date and arrow -->
|
||||
<div :class="[
|
||||
'mt-4 pt-3 flex items-center justify-between',
|
||||
isSelected ? 'border-t border-white/20' : 'border-t border-orange-100'
|
||||
]">
|
||||
<div :class="[
|
||||
'flex items-center text-sm',
|
||||
isSelected ? 'text-white/70' : 'text-slate-500'
|
||||
]">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{{ report.reportDate || '未知时间' }}
|
||||
</div>
|
||||
|
||||
<!-- Arrow indicator -->
|
||||
<div :class="[
|
||||
'w-8 h-8 rounded-full flex items-center justify-center transition-all',
|
||||
isSelected ? 'bg-white/20' : 'bg-orange-100 group-hover:bg-orange-500 group-hover:text-white'
|
||||
]">
|
||||
<svg :class="['w-4 h-4', isSelected ? 'text-white' : 'text-orange-500']" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, h } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
report: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['select'])
|
||||
|
||||
const fileIconMap = {
|
||||
html: { color: 'bg-gradient-to-br from-orange-500 to-orange-600' },
|
||||
md: { color: 'bg-gradient-to-br from-orange-400 to-orange-500' },
|
||||
pptx: { color: 'bg-gradient-to-br from-orange-500 to-orange-600' }
|
||||
}
|
||||
|
||||
const fileTypeLabelMap = {
|
||||
html: 'HTML',
|
||||
md: 'Markdown',
|
||||
pptx: 'PowerPoint'
|
||||
}
|
||||
|
||||
// File icon SVG component
|
||||
const FileIcon = {
|
||||
render() {
|
||||
return h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
|
||||
h('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' })
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const fileIconComponent = computed(() => FileIcon)
|
||||
|
||||
const iconClass = computed(() => {
|
||||
const config = fileIconMap[props.report.fileType] || fileIconMap.html
|
||||
return config.color
|
||||
})
|
||||
|
||||
const fileTypeLabel = computed(() => {
|
||||
return fileTypeLabelMap[props.report.fileType] || props.report.fileType.toUpperCase()
|
||||
})
|
||||
|
||||
const typeBadgeClass = computed(() => {
|
||||
const colors = {
|
||||
html: 'bg-orange-100 text-orange-600',
|
||||
md: 'bg-orange-100 text-orange-600',
|
||||
pptx: 'bg-orange-100 text-orange-600'
|
||||
}
|
||||
return colors[props.report.fileType] || 'bg-orange-100 text-orange-600'
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user