完成首页封面
This commit is contained in:
@@ -1,11 +1,352 @@
|
|||||||
<template>
|
<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>
|
</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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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>
|
</style>
|
||||||
@@ -6,36 +6,21 @@
|
|||||||
height="400px"
|
height="400px"
|
||||||
autoplay
|
autoplay
|
||||||
arrow="always"
|
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">
|
<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">
|
<div class="carousel-caption">
|
||||||
<h3>健康生活环境设计研究成果展</h3>
|
<!-- 标题和描述可后续扩展为接口返回字段,当前先用默认值 -->
|
||||||
<p>探索健康设计新路径,引领人居环境创新</p>
|
<h3>{{ item.title || defaultTitles[index] }}</h3>
|
||||||
</div>
|
<p>{{ item.desc || defaultDescs[index] }}</p>
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-carousel-item>
|
</el-carousel-item>
|
||||||
</el-carousel>
|
</el-carousel>
|
||||||
|
|
||||||
<!-- 新闻动态 -->
|
<!-- 新闻动态 -->
|
||||||
<div class="news-section">
|
<div class="news-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -120,6 +105,7 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { ElCarousel, ElCarouselItem, ElLink, ElCard, ElButton, ElIcon } from 'element-plus';
|
import { ElCarousel, ElCarouselItem, ElLink, ElCard, ElButton, ElIcon } from 'element-plus';
|
||||||
import { ArrowRight } from '@element-plus/icons-vue';
|
import { ArrowRight } from '@element-plus/icons-vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
// 数据结构
|
// 数据结构
|
||||||
interface NewsItem {
|
interface NewsItem {
|
||||||
@@ -188,6 +174,60 @@ onMounted(async () => {
|
|||||||
resourceList.value = resourceData;
|
resourceList.value = resourceData;
|
||||||
communityList.value = communityData;
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user