You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/components/TrainingMaterialButton.vue

169 lines
5.9 KiB
Vue

<template>
<div class="material-actions">
<el-button v-if="caseMaterial && caseMaterial.previewable" class="material-button" @click="previewVisible = true">
<el-icon>
<View />
</el-icon>预览案例资料
</el-button>
<el-button class="material-button" :plain="!caseMaterial?.url" @click="downloadMaterial(caseMaterial, '案例资料')">
<el-icon>
<Download />
</el-icon>案例资料
</el-button>
<el-button class="material-button" :plain="!trainingData?.url" @click="downloadMaterial(trainingData, '实训数据')">
<el-icon>
<Download />
</el-icon>实训数据
</el-button>
<el-button class="material-button" :plain="!guideManual?.url" @click="downloadMaterial(guideManual, '实训指导手册')">
<el-icon>
<Download />
</el-icon>实训指导手册
</el-button>
<el-button class="material-button" :plain="!aiManual?.url" @click="downloadMaterial(aiManual, 'AI辅助指导手册')">
<el-icon>
<Download />
</el-icon>AI辅助指导手册
</el-button>
</div>
<el-dialog v-model="previewVisible" :title="caseMaterial?.name || '案例资料预览'" width="min(920px, 92vw)" destroy-on-close
append-to-body>
<div v-if="isImage" class="image-preview"><img :src="caseMaterial.url" :alt="caseMaterial.name" /></div>
<iframe v-else-if="isPdf" class="pdf-preview" :src="caseMaterial.url" :title="caseMaterial.name"></iframe>
</el-dialog>
</template>
<script setup>
import { computed, ref } from "vue"
import { ElMessage } from "element-plus"
import { Download, View } from "@element-plus/icons-vue"
const props = defineProps({
// 案例资料(向后兼容旧 materialName / materialUrl)
materialName: { type: String, default: "" },
materialUrl: { type: String, default: "" },
caseMaterialName: { type: String, default: "" },
caseMaterialUrl: { type: String, default: "" },
// 实训数据
trainingDataName: { type: String, default: "" },
trainingDataUrl: { type: String, default: "" },
// 实训指导手册
guideManualName: { type: String, default: "" },
guideManualUrl: { type: String, default: "" },
// AI 辅助指导手册
aiManualName: { type: String, default: "" },
aiManualUrl: { type: String, default: "" }
})
const previewVisible = ref(false)
function resolveMaterial(name, url) {
const n = String(name || "").trim()
const u = String(url || "").trim()
if (!n || !u || (n === "案例资料.rar" && u === "/file/example.rar")) return null
return { name: n, url: normalizeMaterialUrl(u) }
}
// 案例资料优先读取新增的 caseMaterialName/Url,缺省时回退到旧 materialName/Url 字段
const caseMaterial = computed(() => resolveMaterial(props.caseMaterialName || props.materialName, props.caseMaterialUrl || props.materialUrl))
const trainingData = computed(() => resolveMaterial(props.trainingDataName, props.trainingDataUrl))
const guideManual = computed(() => resolveMaterial(props.guideManualName, props.guideManualUrl))
const aiManual = computed(() => resolveMaterial(props.aiManualName, props.aiManualUrl))
const extension = computed(() => String(caseMaterial.value?.name || caseMaterial.value?.url || "").split(/[?#]/)[0].split(".").pop().toLowerCase())
const isImage = computed(() => ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(extension.value))
const isPdf = computed(() => extension.value === "pdf")
function normalizeMaterialUrl(rawUrl) {
const url = String(rawUrl || "").trim()
if (!url) return ""
try {
const parsed = new URL(url)
if (["localhost", "127.0.0.1", "::1"].includes(parsed.hostname) && parsed.pathname.startsWith("/file/")) {
return `${parsed.pathname}${parsed.search}${parsed.hash}`
}
} catch {
// Relative file paths are already resolved through the current deployment or Vite proxy.
}
return url
}
async function downloadMaterial(material, label) {
if (!material?.url) {
ElMessage.warning(`暂无${label}`)
return
}
try {
const response = await fetch(material.url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const objectUrl = URL.createObjectURL(await response.blob())
const link = document.createElement("a")
link.href = objectUrl
link.download = material.name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(objectUrl)
} catch (error) {
console.warn(`${label} download failed`, error)
ElMessage.error(`${label}文件不存在或暂不可下载,请联系管理员重新上传。`)
}
}
</script>
<style lang="scss" scoped>
.material-actions {
display: flex;
flex-wrap: wrap;
gap: 6px
}
.material-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 40px;
padding: 0 14px;
border: 1px solid rgba(31, 41, 55, 0.4);
border-radius: 10px;
color: #1f2937;
background: #ffffff;
font-family: inherit;
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease;
}
.material-button:hover {
color: #1f2937;
border-color: #1f2937;
background: #f3f4f6;
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(31, 41, 55, 0.12);
}
.image-preview {
display: flex;
justify-content: center;
max-height: 68vh;
overflow: auto;
background: #0b1720
}
.image-preview img {
display: block;
max-width: 100%;
height: auto;
object-fit: contain
}
.pdf-preview {
width: 100%;
height: 68vh;
border: 0;
background: #fff
}
</style>