fix: update development environment API and refine student training styles
parent
4cb3b8756b
commit
6dc199a4fc
@ -1,71 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="material-actions">
|
<div class="material-actions">
|
||||||
<el-button v-if="material && previewable" class="material-button" @click="previewVisible = true">
|
<el-button v-if="material && previewable" class="material-button" @click="previewVisible = true">
|
||||||
<el-icon><View /></el-icon>预览案例资料
|
<el-icon>
|
||||||
</el-button>
|
<View />
|
||||||
<el-button class="material-button" :plain="!material?.url" @click="handleDownload">
|
</el-icon>预览案例资料
|
||||||
<el-icon><Download /></el-icon>下载案例资料
|
</el-button>
|
||||||
</el-button>
|
<el-button class="material-button" :plain="!material?.url" @click="handleDownload">
|
||||||
</div>
|
<el-icon>
|
||||||
|
<Download />
|
||||||
|
</el-icon>下载案例资料
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-dialog v-model="previewVisible" :title="material?.name || '案例资料预览'" width="min(920px, 92vw)" destroy-on-close append-to-body>
|
<el-dialog v-model="previewVisible" :title="material?.name || '案例资料预览'" width="min(920px, 92vw)" destroy-on-close
|
||||||
<div v-if="isImage" class="image-preview"><img :src="material.url" :alt="material.name" /></div>
|
append-to-body>
|
||||||
<iframe v-else-if="isPdf" class="pdf-preview" :src="material.url" :title="material.name"></iframe>
|
<div v-if="isImage" class="image-preview"><img :src="material.url" :alt="material.name" /></div>
|
||||||
</el-dialog>
|
<iframe v-else-if="isPdf" class="pdf-preview" :src="material.url" :title="material.name"></iframe>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue"
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus"
|
||||||
import { Download, View } from "@element-plus/icons-vue";
|
import { Download, View } from "@element-plus/icons-vue"
|
||||||
|
|
||||||
const props = defineProps({ materialName: { type: String, default: "" }, materialUrl: { type: String, default: "" } });
|
const props = defineProps({ materialName: { type: String, default: "" }, materialUrl: { type: String, default: "" } })
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false)
|
||||||
const material = computed(() => {
|
const material = computed(() => {
|
||||||
const name = String(props.materialName || "").trim();
|
const name = String(props.materialName || "").trim()
|
||||||
const url = String(props.materialUrl || "").trim();
|
const url = String(props.materialUrl || "").trim()
|
||||||
if (!name || !url || (name === "案例资料.rar" && url === "/file/example.rar")) return null;
|
if (!name || !url || (name === "案例资料.rar" && url === "/file/example.rar")) return null
|
||||||
return { name, url: normalizeMaterialUrl(url) };
|
return { name, url: normalizeMaterialUrl(url) }
|
||||||
});
|
})
|
||||||
const extension = computed(() => String(material.value?.name || material.value?.url || "").split(/[?#]/)[0].split(".").pop().toLowerCase());
|
const extension = computed(() => String(material.value?.name || material.value?.url || "").split(/[?#]/)[0].split(".").pop().toLowerCase())
|
||||||
const isImage = computed(() => ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(extension.value));
|
const isImage = computed(() => ["jpg", "jpeg", "png", "gif", "webp", "bmp", "svg"].includes(extension.value))
|
||||||
const isPdf = computed(() => extension.value === "pdf");
|
const isPdf = computed(() => extension.value === "pdf")
|
||||||
const previewable = computed(() => isImage.value || isPdf.value);
|
const previewable = computed(() => isImage.value || isPdf.value)
|
||||||
|
|
||||||
function normalizeMaterialUrl(rawUrl) {
|
function normalizeMaterialUrl(rawUrl) {
|
||||||
const url = String(rawUrl || "").trim();
|
const url = String(rawUrl || "").trim()
|
||||||
if (!url) return "";
|
if (!url) return ""
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url)
|
||||||
if (["localhost", "127.0.0.1", "::1"].includes(parsed.hostname) && parsed.pathname.startsWith("/file/")) {
|
if (["localhost", "127.0.0.1", "::1"].includes(parsed.hostname) && parsed.pathname.startsWith("/file/")) {
|
||||||
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
return `${parsed.pathname}${parsed.search}${parsed.hash}`
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Relative file paths are already resolved through the current deployment or Vite proxy.
|
||||||
}
|
}
|
||||||
} catch {
|
return url
|
||||||
// Relative file paths are already resolved through the current deployment or Vite proxy.
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownload() {
|
async function handleDownload() {
|
||||||
if (!material.value?.url) { ElMessage.warning("暂无案例资料"); return; }
|
if (!material.value?.url) { ElMessage.warning("暂无案例资料"); return }
|
||||||
try {
|
try {
|
||||||
const response = await fetch(material.value.url);
|
const response = await fetch(material.value.url)
|
||||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
||||||
const objectUrl = URL.createObjectURL(await response.blob());
|
const objectUrl = URL.createObjectURL(await response.blob())
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a")
|
||||||
link.href = objectUrl;
|
link.href = objectUrl
|
||||||
link.download = material.value.name;
|
link.download = material.value.name
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link)
|
||||||
link.click();
|
link.click()
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link)
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Case material download failed", error);
|
console.warn("Case material download failed", error)
|
||||||
ElMessage.error("案例资料文件不存在或暂不可下载,请联系管理员重新上传。");
|
ElMessage.error("案例资料文件不存在或暂不可下载,请联系管理员重新上传。")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.material-actions{display:flex;flex-wrap:wrap;gap:8px}.material-button{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;padding:0 10px;border-color:rgba(99,217,255,.55);border-radius:7px;color:#e9fbff;background:rgba(17,126,178,.36);font-size:13px;font-weight:800}.material-button:hover{color:#e9fbff;border-color:rgba(116,235,255,.85);background:rgba(18,146,204,.5)}.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}
|
.material-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-color: rgba(99, 217, 255, .55);
|
||||||
|
border-radius: var(--student-radius-sm);
|
||||||
|
color: #e9fbff;
|
||||||
|
background: rgba(17, 126, 178, .36);
|
||||||
|
font-size: var(--student-step-font);
|
||||||
|
font-weight: 800
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-button:hover {
|
||||||
|
color: #e9fbff;
|
||||||
|
border-color: rgba(116, 235, 255, .85);
|
||||||
|
background: rgba(18, 146, 204, .5)
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue