完成首页封面
This commit is contained in:
@@ -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>
|
||||
@@ -6,36 +6,21 @@
|
||||
height="400px"
|
||||
autoplay
|
||||
arrow="always"
|
||||
v-if="carouselImages.length > 0"
|
||||
>
|
||||
<el-carousel-item>
|
||||
<!-- 动态循环生成轮播项 -->
|
||||
<el-carousel-item v-for="(item, index) in carouselImages" :key="item.id || index">
|
||||
<div class="carousel-item">
|
||||
<img src="https://picsum.photos/1920/400?random=1" alt="健康生活研究" class="carousel-img">
|
||||
<!-- 绑定接口返回的图片URL -->
|
||||
<img :src="item.image_url" :alt="item.alt || `轮播图${index+1}`" class="carousel-img">
|
||||
<div class="carousel-caption">
|
||||
<h3>健康生活环境设计研究成果展</h3>
|
||||
<p>探索健康设计新路径,引领人居环境创新</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item>
|
||||
<div class="carousel-item">
|
||||
<img src="https://picsum.photos/1920/400?random=2" alt="学术研讨会" class="carousel-img">
|
||||
<div class="carousel-caption">
|
||||
<h3>2025健康设计国际学术研讨会</h3>
|
||||
<p>汇聚全球专家智慧,共话健康人居未来</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item>
|
||||
<div class="carousel-item">
|
||||
<img src="https://picsum.photos/1920/400?random=3" alt="社区服务案例" class="carousel-img">
|
||||
<div class="carousel-caption">
|
||||
<h3>社区健康服务创新实践</h3>
|
||||
<p>以设计赋能社区,提升居民生活品质</p>
|
||||
<!-- 标题和描述可后续扩展为接口返回字段,当前先用默认值 -->
|
||||
<h3>{{ item.title || defaultTitles[index] }}</h3>
|
||||
<p>{{ item.desc || defaultDescs[index] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
<!-- 新闻动态 -->
|
||||
<div class="news-section">
|
||||
<div class="section-header">
|
||||
@@ -120,6 +105,7 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElCarousel, ElCarouselItem, ElLink, ElCard, ElButton, ElIcon } from 'element-plus';
|
||||
import { ArrowRight } from '@element-plus/icons-vue';
|
||||
import axios from 'axios';
|
||||
|
||||
// 数据结构
|
||||
interface NewsItem {
|
||||
@@ -188,6 +174,60 @@ onMounted(async () => {
|
||||
resourceList.value = resourceData;
|
||||
communityList.value = communityData;
|
||||
});
|
||||
|
||||
const defaultTitles = [
|
||||
'健康生活环境设计研究成果展',
|
||||
'2025健康设计国际学术研讨会',
|
||||
'社区健康服务创新实践'
|
||||
];
|
||||
const defaultDescs = [
|
||||
'探索健康设计新路径,引领人居环境创新',
|
||||
'汇聚全球专家智慧,共话健康人居未来',
|
||||
'以设计赋能社区,提升居民生活品质'
|
||||
];
|
||||
|
||||
|
||||
interface CarouselImage {
|
||||
id?: number; // 可选,数据库返回的ID
|
||||
image_url: string; // 图片URL(必选)
|
||||
sort?: number; // 可选,排序字段
|
||||
// 其他可选字段(如title、desc等)
|
||||
title?: string;
|
||||
desc?: string;
|
||||
alt?: string;
|
||||
}
|
||||
|
||||
// 2. 显式指定泛型类型为 CarouselImage[]
|
||||
const carouselImages = ref<CarouselImage[]>([]);
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 发送 POST 请求,JSON 体传参
|
||||
const response = await axios.post(
|
||||
'http://localhost:8080/api/page-image/get',
|
||||
{ page: 'home' } // JSON 格式传递 page 参数
|
||||
);
|
||||
console.log("首页图片查询成功",response.data.images);
|
||||
// 检查响应是否成功
|
||||
if (response.data.message === '查询成功') {
|
||||
|
||||
|
||||
// 按 sort 排序(处理可能的 undefined)
|
||||
carouselImages.value = response.data.images
|
||||
|
||||
console.log("赋值后:",carouselImages.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error);
|
||||
// 兜底数据
|
||||
carouselImages.value = [
|
||||
{ image_url: 'https://picsum.photos/1920/400?random=1' },
|
||||
{ image_url: 'https://picsum.photos/1920/400?random=2' },
|
||||
{ image_url: 'https://picsum.photos/1920/400?random=3' }
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user