完成首页封面

This commit is contained in:
2025-10-27 17:22:58 +08:00
parent a5285d7dc0
commit 8d87c9662e
2 changed files with 406 additions and 25 deletions

View File

@@ -1,11 +1,352 @@
<template>
<div class="carousel-management">
<el-card class="page-card" header="首页轮播图管理">
<p class="card-desc">管理首页轮播图共3张按排序1-3显示</p>
<!-- 轮播图列表固定3个位置 -->
<div class="carousel-list">
<div
v-for="(item, index) in carouselItems"
:key="index"
class="carousel-item"
>
<div class="item-header">
<span class="item-index">轮播图 {{ index + 1 }}</span>
<span class="item-sort">排序值{{ index + 1 }}</span>
</div>
<!-- 已上传图片预览 -->
<div v-if="item.imageUrl" class="image-preview">
<img :src="item.imageUrl" :alt="`轮播图${index + 1}`" class="preview-img">
<button
class="remove-btn"
@click="handleRemove(index)"
:disabled="isSubmitting"
title="删除图片"
>
<el-icon><Close /></el-icon>
</button>
</div>
<!-- 未上传时的上传区域 -->
<el-upload
v-else
class="cover-upload-area"
action="http://localhost:8080/api/upload/cover"
name="image"
:show-file-list="false"
:on-success="(response) => handleUploadSuccess(response, index)"
:before-upload="beforeUpload"
:on-error="(error) => handleUploadError(error, index)"
:disabled="isSubmitting"
>
<div class="upload-placeholder">
<el-icon class="upload-icon"><Upload /></el-icon>
<p class="upload-text">点击上传轮播图 {{ index + 1 }}</p>
<p class="upload-subtext">建议16:9比例支持JPG/PNG/WEBP≤5MB</p>
</div>
</el-upload>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button
type="primary"
@click="saveAll"
:loading="isSubmitting"
:disabled="!hasModified"
>
保存全部修改
</el-button>
<el-button
@click="fetchCarouselImages"
:loading="isSubmitting"
>
刷新数据
</el-button>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import axios from 'axios';
import { Close, Upload } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
// 轮播图数据结构
interface CarouselItem {
id?: number; // 数据库ID编辑时使用
imageUrl: string; // 图片URL
sort: number; // 排序值1-3
}
// 轮播图当前数据固定3个位置
const carouselItems = ref<CarouselItem[]>([
{ imageUrl: '', sort: 1 },
{ imageUrl: '', sort: 2 },
{ imageUrl: '', sort: 3 }
]);
// 初始数据(用于对比修改)
const initialItems = ref<CarouselItem[]>([]);
const isSubmitting = ref(false);
// 检查是否有修改(用于禁用保存按钮)
const hasModified = computed(() => {
return carouselItems.value.some((item, index) => {
const initial = initialItems.value[index];
return item.imageUrl !== initial?.imageUrl;
});
});
// 页面挂载时加载数据
onMounted(() => {
console.log('API 基础地址:', import.meta.env.VITE_API_BASE_URL);
fetchCarouselImages().then(() => {
// 深拷贝初始数据(避免引用关联)
initialItems.value = JSON.parse(JSON.stringify(carouselItems.value));
});
});
// 获取当前轮播图数据
const fetchCarouselImages = async () => {
try {
isSubmitting.value = true;
const response = await axios.post('http://localhost:8080/api/page-image/get', { page: 'home' });
if (response.data.message === '查询成功' && Array.isArray(response.data.images)) {
// 按排序值匹配到对应位置
const images = response.data.images.sort((a: any, b: any) => a.sort - b.sort);
images.forEach((img: any, idx: number) => {
if (carouselItems.value[idx]) {
carouselItems.value[idx] = {
...carouselItems.value[idx],
id: img.id,
imageUrl: img.image_url || ''
};
}
});
}
} catch (error) {
console.error('获取轮播图失败:', error);
ElMessage.error('获取轮播图数据失败');
} finally {
isSubmitting.value = false;
}
};
import type { UploadProps } from 'element-plus';
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
const allowTypes = ['image/jpeg', 'image/png', 'image/webp'];
if (!allowTypes.includes(rawFile.type)) {
ElMessage.error('仅支持JPG/PNG/WEBP格式的图片');
return false;
}
if (rawFile.size / 1024 / 1024 > 5) {
ElMessage.error('图片大小不能超过5MB');
return false;
}
console.log("检验成功");
return true;
};
// 上传成功处理
const handleUploadSuccess = (response: any, index: number) => {
console.log("上传结果:", response);
const ossUrl = response.data?.url;
if (ossUrl) {
carouselItems.value[index].imageUrl = ossUrl;
ElMessage.success(`轮播图 ${index + 1} 上传成功`);
} else {
ElMessage.error(`轮播图 ${index + 1} 上传失败`);
}
};
// 上传失败处理
const handleUploadError = (error: any, index: number) => {
console.error(`轮播图 ${index + 1} 上传错误:`, error);
ElMessage.error(`轮播图 ${index + 1} 上传失败,请重试`);
};
// 删除图片
const handleRemove = (index: number) => {
carouselItems.value[index].imageUrl = '';
ElMessage.info(`轮播图 ${index + 1} 已移除`);
};
// 保存全部修改(仅提交有变化的图片)
const saveAll = async () => {
try {
isSubmitting.value = true;
// 过滤需要修改的图片有id且URL有变化
const modifiedItems = carouselItems.value.filter((item, index) => {
const initial = initialItems.value[index];
return item.id && item.imageUrl && item.imageUrl !== initial?.imageUrl;
});
if (modifiedItems.length === 0) {
ElMessage.info('没有需要保存的修改');
return;
}
console.log("有更改的img",modifiedItems);
//批量提交修改仅含id和image_url
for (const item of modifiedItems) {
await axios.post('http://localhost:8080/api/page-image/save', {
id: item.id,
image_url: item.imageUrl
});
}
ElMessage.success(`成功保存 ${modifiedItems.length} 张轮播图`);
// 更新初始数据(避免下次误判)
initialItems.value = JSON.parse(JSON.stringify(carouselItems.value));
} catch (error) {
console.error('保存轮播图失败:', error);
ElMessage.error('保存失败,请重试');
} finally {
isSubmitting.value = false;
}
};
</script>
<style scoped>
.carousel-management {
padding: 20px;
}
.page-card {
max-width: 1200px;
margin: 0 auto;
}
.card-desc {
color: #666;
margin-bottom: 20px;
font-size: 14px;
}
.carousel-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.carousel-item {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
position: relative;
}
.item-header {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
font-size: 14px;
}
.item-index {
font-weight: 500;
color: #333;
}
.item-sort {
color: #666;
font-size: 12px;
}
.image-preview {
width: 100%;
height: 200px;
border-radius: 4px;
overflow: hidden;
position: relative;
background: #f5f5f5;
}
.preview-img {
width: 100%;
height: 100%;
object-fit: cover;
transition: all 0.3s;
}
.preview-img:hover {
transform: scale(1.02);
}
.remove-btn {
position: absolute;
top: 10px;
right: 10px;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s;
}
.remove-btn:hover {
background: rgba(255, 0, 0, 0.7);
}
/* 复用封面上传样式 */
.cover-upload-area {
width: 100%;
height: 200px;
}
.upload-placeholder {
width: 100%;
height: 100%;
border: 2px dashed #ccc;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #fafafa;
cursor: pointer;
transition: all 0.3s;
}
.upload-placeholder:hover {
border-color: #409eff;
background: #f0f7ff;
}
.upload-icon {
font-size: 32px;
color: #999;
margin-bottom: 10px;
}
.upload-text {
color: #666;
margin-bottom: 5px;
}
.upload-subtext {
color: #999;
font-size: 12px;
}
.action-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
</style>