大屏产品中心-产品案例开发
This commit is contained in:
parent
d5aec763e5
commit
ff8449d7a4
|
|
@ -17,3 +17,13 @@ export function getProductCenterDetailAPI(data) {
|
|||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询产品中心详情
|
||||
export function getProductCenterCaseDetailAPI(data) {
|
||||
return request({
|
||||
url: '/product/screen/getProductDetailsById',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,19 @@ export const constantRoutes = [
|
|||
name: 'ProductDetail',
|
||||
meta: { showSearch: false } // 不需要搜索框
|
||||
},
|
||||
|
||||
|
||||
// 产品中心 ---- 查看产品案例详情页面
|
||||
{
|
||||
path: 'productCenter/caseDetail/:id',
|
||||
component: () =>
|
||||
import(
|
||||
'@/views/publicService/productCenter/product-case-detail.vue'
|
||||
),
|
||||
name: 'ProductCaseDetail',
|
||||
meta: { showSearch: false } // 不需要搜索框
|
||||
},
|
||||
|
||||
// 产品中心 ---- 查看案例页面
|
||||
{
|
||||
path: 'productCenter/caseList',
|
||||
|
|
|
|||
|
|
@ -193,16 +193,15 @@
|
|||
<el-form-item
|
||||
label="案例图片"
|
||||
prop="caseImage"
|
||||
required
|
||||
>
|
||||
<UploadImgFormData
|
||||
:limit="6"
|
||||
:limit="10"
|
||||
:file-size="10"
|
||||
:file-type="['jpg', 'png', 'jpeg']"
|
||||
:file-list.sync="item.formInfo.caseImage"
|
||||
@onSelectDeleteFileId="handleSelectDeleteFileId"
|
||||
:is-uploaded="
|
||||
item.formInfo.caseImage.length >= 6
|
||||
item.formInfo.caseImage.length >= 10
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
|
@ -324,10 +323,31 @@ export default {
|
|||
],
|
||||
caseImage: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
message: '请上传案例图片',
|
||||
},
|
||||
validator: (rule, value, callback) => {
|
||||
console.log('【校验案例图片】当前图片列表:', value);
|
||||
|
||||
// 确保是数组
|
||||
if (!Array.isArray(value)) {
|
||||
return callback(new Error('请上传案例图片'));
|
||||
}
|
||||
|
||||
// 过滤出上传成功或已有 url 的有效图片
|
||||
const validFiles = value.filter(file => {
|
||||
return file.url || (file.response && file.response.url) || file.status === 'success';
|
||||
});
|
||||
|
||||
if (validFiles.length === 0) {
|
||||
callback(new Error('请上传案例图片'));
|
||||
} else if (validFiles.length < 5) {
|
||||
callback(new Error(`请至少上传 5 张案例图片,当前已上传 ${validFiles.length} 张`));
|
||||
} else if (validFiles.length > 10) {
|
||||
callback(new Error('最多上传 10 张案例图片'));
|
||||
} else {
|
||||
callback(); // 校验通过
|
||||
}
|
||||
},
|
||||
trigger: 'change' // 图片变化时触发
|
||||
}
|
||||
],
|
||||
caseSort: [
|
||||
{required: true, message: '请输入排序', trigger: 'blur'},
|
||||
|
|
@ -464,13 +484,25 @@ export default {
|
|||
}
|
||||
})
|
||||
|
||||
// 检查案例图片(如果需要必填的话)
|
||||
if (isValid && tab.formInfo.caseImage.length === 0) {
|
||||
isValid = false
|
||||
errorMessage = `案例${
|
||||
tabIndex + 1
|
||||
}请至少上传一张案例图片`
|
||||
errorCaseIndex = tabIndex
|
||||
// 检查图片数量(5~10 张)
|
||||
if (isValid) {
|
||||
const validImages = tab.formInfo.caseImage.filter(file => {
|
||||
return file.url || (file.response && file.response.url) || file.status === 'success'
|
||||
})
|
||||
|
||||
if (validImages.length === 0) {
|
||||
isValid = false
|
||||
errorMessage = `案例${tabIndex + 1}请上传案例图片`
|
||||
errorCaseIndex = tabIndex
|
||||
} else if (validImages.length < 5) {
|
||||
isValid = false
|
||||
errorMessage = `案例${tabIndex + 1}请至少上传 5 张案例图片,当前已上传 ${validImages.length} 张`
|
||||
errorCaseIndex = tabIndex
|
||||
} else if (validImages.length > 10) {
|
||||
isValid = false
|
||||
errorMessage = `案例${tabIndex + 1}最多上传 10 张案例图片,当前已上传 ${validImages.length} 张`
|
||||
errorCaseIndex = tabIndex
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,417 @@
|
|||
<template>
|
||||
<!-- 产品案例 -->
|
||||
<div class="case-container">
|
||||
<div class="case-header">
|
||||
<span> {{ cardTitle }} </span>
|
||||
<span @click="onHandleViewMore"> 查看更多 </span>
|
||||
</div>
|
||||
|
||||
<!-- 轮播容器 -->
|
||||
<div class="carousel-wrapper">
|
||||
<!-- 轮播轨道 - 使用transition实现平滑过渡 -->
|
||||
<div
|
||||
class="carousel-track"
|
||||
:style="{
|
||||
transform: `translateX(-${currentIndex * 100}%)`,
|
||||
transition: transitionDuration,
|
||||
}"
|
||||
>
|
||||
<!-- 复制第一个元素到末尾,最后一个元素到开头,实现无缝衔接 -->
|
||||
<div
|
||||
:key="index"
|
||||
class="case-item"
|
||||
v-for="(item, index) in extendedCardList"
|
||||
>
|
||||
<div class="case-left-img">
|
||||
<el-carousel height="160px">
|
||||
<el-carousel-item
|
||||
v-for="j in item.imageList"
|
||||
:key="j.id"
|
||||
>
|
||||
<el-image
|
||||
:src="j.url"
|
||||
fit="cover"
|
||||
style="width: 320px; height: 160px;"
|
||||
/>
|
||||
<!-- <ImagePreview
|
||||
:height="160"
|
||||
:width="320"
|
||||
:src1="j.url"
|
||||
:isShowMask="true"
|
||||
/>-->
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
|
||||
<div class="case-right-content">
|
||||
<h3>{{ item.caseCompany }}</h3>
|
||||
<!-- <div>{{ item.caseIntroduction }} </div> -->
|
||||
<div v-if="item.isTextOverflow">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div
|
||||
slot="content"
|
||||
class="product-info-tooltip"
|
||||
>
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
<div class="product-info-content">
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="product-info-content"
|
||||
v-else
|
||||
:ref="`caseInfoContent-${index}`"
|
||||
>
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左右控制按钮 -->
|
||||
<img
|
||||
alt="向左切换"
|
||||
class="arrow-left"
|
||||
@click="prevSlide"
|
||||
v-if="cardList.length > 1"
|
||||
src="@/assets/images/publicService/arrow-left.png"
|
||||
/>
|
||||
<img
|
||||
alt="向右切换"
|
||||
@click="nextSlide"
|
||||
class="arrow-right"
|
||||
v-if="cardList.length > 1"
|
||||
src="@/assets/images/publicService/arrow-right.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextTip from '@/components/TextTip/index'
|
||||
export default {
|
||||
name: 'CaseContainer',
|
||||
components: {
|
||||
TextTip,
|
||||
},
|
||||
props: {
|
||||
cardTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
cardList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
productId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0, // 当前显示的索引,从1开始(因为我们在开头添加了最后一个元素的副本)
|
||||
transitionDuration: '0.5s', // 过渡动画时长
|
||||
timer: null, // 自动轮播计时器
|
||||
interval: 5000, // 轮播间隔时间(ms)
|
||||
extendedCardList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 扩展列表:在开头添加最后一个元素的副本,在末尾添加第一个元素的副本,实现无缝滚动
|
||||
// extendedCardList() {
|
||||
// if (this.cardList.length <= 1) return this.cardList
|
||||
// return [
|
||||
// this.cardList[this.cardList.length - 1], // 最后一个元素的副本放在开头
|
||||
// ...this.cardList, // 原始列表
|
||||
// this.cardList[0], // 第一个元素的副本放在末尾
|
||||
// ]
|
||||
// },
|
||||
},
|
||||
mounted() {
|
||||
// 只有在有多项数据时才启动自动轮播
|
||||
if (this.cardList.length > 1) {
|
||||
this.startAutoPlay()
|
||||
}
|
||||
|
||||
// 监听鼠标进入/离开,暂停/恢复自动轮播
|
||||
const container = this.$el.querySelector('.carousel-wrapper')
|
||||
container.addEventListener('mouseenter', this.stopAutoPlay)
|
||||
container.addEventListener('mouseleave', this.startAutoPlay)
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理计时器
|
||||
this.stopAutoPlay()
|
||||
|
||||
// 移除事件监听
|
||||
const container = this.$el.querySelector('.carousel-wrapper')
|
||||
if (container) {
|
||||
container.removeEventListener('mouseenter', this.stopAutoPlay)
|
||||
container.removeEventListener('mouseleave', this.startAutoPlay)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 开始自动轮播
|
||||
startAutoPlay() {
|
||||
// 只有在有多项数据时才启动自动轮播
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
this.stopAutoPlay() // 先清除已有计时器
|
||||
this.timer = setInterval(() => {
|
||||
this.nextSlide() // 自动轮播方向:从左到右
|
||||
}, this.interval)
|
||||
},
|
||||
|
||||
// 停止自动轮播
|
||||
stopAutoPlay() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
// 下一张(向右切换)- 从左到右显示
|
||||
nextSlide() {
|
||||
// 如果只有一项数据,不执行切换
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// 当到达最后一个真实元素时,切换到复制的第一个元素
|
||||
if (this.currentIndex === this.cardList.length) {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex++
|
||||
|
||||
// 动画完成后,瞬间切换到真实的第一个元素
|
||||
setTimeout(() => {
|
||||
this.transitionDuration = '0s'
|
||||
this.currentIndex = 1
|
||||
}, 500)
|
||||
} else {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex++
|
||||
}
|
||||
},
|
||||
|
||||
// 上一张(向左切换)- 从右到左显示
|
||||
prevSlide() {
|
||||
// 如果只有一项数据,不执行切换
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// 当在第一个真实元素时,先切换到复制的最后一个元素
|
||||
if (this.currentIndex === 1) {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex--
|
||||
|
||||
// 动画完成后,瞬间切换到真实的最后一个元素
|
||||
setTimeout(() => {
|
||||
this.transitionDuration = '0s'
|
||||
this.currentIndex = this.cardList.length
|
||||
}, 500)
|
||||
} else {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex--
|
||||
}
|
||||
},
|
||||
|
||||
// 查看更多
|
||||
onHandleViewMore() {
|
||||
this.$router.push({
|
||||
name: 'ProductCaseList',
|
||||
query: {
|
||||
id: this.productId,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
cardList: {
|
||||
handler(newValue) {
|
||||
if (newValue <= 1) {
|
||||
this.extendedCardList = newValue
|
||||
} else {
|
||||
this.extendedCardList = [
|
||||
this.cardList[this.cardList.length - 1], // 最后一个元素的副本放在开头
|
||||
...this.cardList, // 原始列表
|
||||
this.cardList[0], // 第一个元素的副本放在末尾
|
||||
]
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.extendedCardList.forEach((item, index) => {
|
||||
item.isTextOverflow =
|
||||
this.$refs[`caseInfoContent-${index}`][0]
|
||||
.clientHeight <
|
||||
this.$refs[`caseInfoContent-${index}`][0]
|
||||
.scrollHeight
|
||||
})
|
||||
})
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.case-container {
|
||||
width: 100%;
|
||||
padding: 0 14px 20px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.case-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
|
||||
& span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
& span:last-child {
|
||||
font-size: 14px;
|
||||
background: linear-gradient(90deg, #00c7ef 0%, #005eef 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
// 轮播容器样式 - 确保不会显示溢出内容
|
||||
.carousel-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
// padding: 0 10px;
|
||||
// 添加额外的裁剪区域,确保不会显示相邻项的边缘
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
// 轮播轨道样式
|
||||
.carousel-track {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
will-change: transform; // 优化动画性能
|
||||
}
|
||||
|
||||
// 单个案例项样式 - 确保占满整个宽度,不会露出相邻项
|
||||
.case-item {
|
||||
width: 100%; // 确保每个项目占满容器宽度
|
||||
flex-shrink: 0; // 防止项目被压缩
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arrow-left,
|
||||
.arrow-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
z-index: 10; // 确保按钮在内容上方
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.arrow-left:hover,
|
||||
.arrow-right:hover {
|
||||
background-color: white;
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
.case-left-img {
|
||||
width: 320px;
|
||||
height: 160px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.case-right-content {
|
||||
flex: 1;
|
||||
height: 160px;
|
||||
padding-left: 36px;
|
||||
background-color: #f0f0f0;
|
||||
|
||||
h3 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-top: 36px;
|
||||
height: 70px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
div {
|
||||
// width: 100%;
|
||||
// height: 90px;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1.8;
|
||||
display: -webkit-box; /* 将元素作为弹性伸缩盒子模型显示 */
|
||||
-webkit-box-orient: vertical; /* 设置伸缩盒子的子元素排列方式为垂直排列 */
|
||||
-webkit-line-clamp: 2; /* 限制显示的行数为4行 */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-carousel__arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .el-carousel__button {
|
||||
background-color: #827e7e;
|
||||
height: 4px;
|
||||
width: 18px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.product-info-tooltip {
|
||||
max-width: 80vw !important;
|
||||
padding: 6px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,417 +1,279 @@
|
|||
<template>
|
||||
<!-- 产品案例 -->
|
||||
<div class="case-container">
|
||||
<div class="case-header">
|
||||
<span> {{ cardTitle }} </span>
|
||||
<span @click="onHandleViewMore"> 查看更多 </span>
|
||||
</div>
|
||||
|
||||
<!-- 轮播容器 -->
|
||||
<div class="carousel-wrapper">
|
||||
<!-- 轮播轨道 - 使用transition实现平滑过渡 -->
|
||||
<div
|
||||
class="carousel-track"
|
||||
:style="{
|
||||
transform: `translateX(-${currentIndex * 100}%)`,
|
||||
transition: transitionDuration,
|
||||
}"
|
||||
>
|
||||
<!-- 复制第一个元素到末尾,最后一个元素到开头,实现无缝衔接 -->
|
||||
<div
|
||||
:key="index"
|
||||
class="case-item"
|
||||
v-for="(item, index) in extendedCardList"
|
||||
>
|
||||
<div class="case-left-img">
|
||||
<el-carousel height="160px">
|
||||
<el-carousel-item
|
||||
v-for="j in item.imageList"
|
||||
:key="j.id"
|
||||
>
|
||||
<el-image
|
||||
:src="j.url"
|
||||
fit="cover"
|
||||
style="width: 320px; height: 160px;"
|
||||
/>
|
||||
<!-- <ImagePreview
|
||||
:height="160"
|
||||
:width="320"
|
||||
:src1="j.url"
|
||||
:isShowMask="true"
|
||||
/>-->
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
|
||||
<div class="case-right-content">
|
||||
<h3>{{ item.caseCompany }}</h3>
|
||||
<!-- <div>{{ item.caseIntroduction }} </div> -->
|
||||
<div v-if="item.isTextOverflow">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div
|
||||
slot="content"
|
||||
class="product-info-tooltip"
|
||||
>
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
<div class="product-info-content">
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="product-info-content"
|
||||
v-else
|
||||
:ref="`caseInfoContent-${index}`"
|
||||
>
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左右控制按钮 -->
|
||||
<img
|
||||
alt="向左切换"
|
||||
class="arrow-left"
|
||||
@click="prevSlide"
|
||||
v-if="cardList.length > 1"
|
||||
src="@/assets/images/publicService/arrow-left.png"
|
||||
/>
|
||||
<img
|
||||
alt="向右切换"
|
||||
@click="nextSlide"
|
||||
class="arrow-right"
|
||||
v-if="cardList.length > 1"
|
||||
src="@/assets/images/publicService/arrow-right.png"
|
||||
/>
|
||||
</div>
|
||||
<!-- 产品案例 -->
|
||||
<div class="case-container">
|
||||
<div class="case-header">
|
||||
<span> {{ cardTitle }} </span>
|
||||
<!-- <span @click="onHandleViewMore"> 查看更多 </span>-->
|
||||
<span ></span>
|
||||
</div>
|
||||
|
||||
<!-- 案例网格容器 -->
|
||||
<div class="case-grid-wrapper">
|
||||
<!-- 案例网格 -->
|
||||
<div class="case-grid">
|
||||
<div
|
||||
v-for="(item, index) in displayedCardList"
|
||||
:key="index"
|
||||
class="case-item-grid"
|
||||
>
|
||||
<div class="case-left-img" @click="handleCaseDetail(item)">
|
||||
<!-- <el-carousel height="200px">
|
||||
<el-carousel-item
|
||||
v-for="j in item.imageList"
|
||||
:key="j.id"
|
||||
>
|
||||
<el-image
|
||||
:src="j.url"
|
||||
fit="cover"
|
||||
style="width: 100%; height: 200px;"
|
||||
/>
|
||||
</el-carousel-item>
|
||||
</el-carousel>-->
|
||||
|
||||
<!-- <el-image-->
|
||||
<!-- :src="item.imageList[0]?.url"-->
|
||||
<!-- style="width: 100%; height: 200px;"-->
|
||||
<!-- />-->
|
||||
<el-image
|
||||
:src="item.imageList[0].url"
|
||||
fit="cover"
|
||||
style="width: 100%; height: 200px;"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="case-right-content">
|
||||
<div>{{ item.caseCompany }}</div>
|
||||
<!-- <div v-if="item.isTextOverflow">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<div slot="content" class="product-info-tooltip">
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
<div class="product-info-content">
|
||||
{{ item.caseIntroduction }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-else class="product-info-content">
|
||||
{{ item.caseIntroduction }}
|
||||
</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多按钮 -->
|
||||
<div v-if="cardList.length > 8 && showLoadMoreButton" class="load-more-btn">
|
||||
<el-button
|
||||
type="text"
|
||||
@click="showAllCases"
|
||||
size="small"
|
||||
style="color: #999; font-size: 14px; padding: 8px 16px; border-radius: 20px; border: 1px solid #e5e5e5;"
|
||||
>
|
||||
加载更多
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextTip from '@/components/TextTip/index'
|
||||
export default {
|
||||
name: 'CaseContainer',
|
||||
components: {
|
||||
TextTip,
|
||||
name: 'CaseContainer',
|
||||
props: {
|
||||
cardTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
props: {
|
||||
cardTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
cardList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
productId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
cardList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0, // 当前显示的索引,从1开始(因为我们在开头添加了最后一个元素的副本)
|
||||
transitionDuration: '0.5s', // 过渡动画时长
|
||||
timer: null, // 自动轮播计时器
|
||||
interval: 5000, // 轮播间隔时间(ms)
|
||||
extendedCardList: [],
|
||||
}
|
||||
productId: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
computed: {
|
||||
// 扩展列表:在开头添加最后一个元素的副本,在末尾添加第一个元素的副本,实现无缝滚动
|
||||
// extendedCardList() {
|
||||
// if (this.cardList.length <= 1) return this.cardList
|
||||
// return [
|
||||
// this.cardList[this.cardList.length - 1], // 最后一个元素的副本放在开头
|
||||
// ...this.cardList, // 原始列表
|
||||
// this.cardList[0], // 第一个元素的副本放在末尾
|
||||
// ]
|
||||
// },
|
||||
},
|
||||
mounted() {
|
||||
// 只有在有多项数据时才启动自动轮播
|
||||
if (this.cardList.length > 1) {
|
||||
this.startAutoPlay()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
displayedCardList: [],
|
||||
showAll: false,
|
||||
showLoadMoreButton: true, // 添加这个状态控制按钮显示
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cardList: {
|
||||
handler(newValue) {
|
||||
if (newValue.length <= 8) {
|
||||
this.displayedCardList = newValue;
|
||||
this.showAll = true;
|
||||
} else {
|
||||
this.displayedCardList = newValue.slice(0, 8);
|
||||
this.showAll = false;
|
||||
}
|
||||
|
||||
// 监听鼠标进入/离开,暂停/恢复自动轮播
|
||||
const container = this.$el.querySelector('.carousel-wrapper')
|
||||
container.addEventListener('mouseenter', this.stopAutoPlay)
|
||||
container.addEventListener('mouseleave', this.startAutoPlay)
|
||||
this.$nextTick(() => {
|
||||
this.updateTextOverflow();
|
||||
});
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理计时器
|
||||
this.stopAutoPlay()
|
||||
|
||||
// 移除事件监听
|
||||
const container = this.$el.querySelector('.carousel-wrapper')
|
||||
if (container) {
|
||||
container.removeEventListener('mouseenter', this.stopAutoPlay)
|
||||
container.removeEventListener('mouseleave', this.startAutoPlay)
|
||||
},
|
||||
methods: {
|
||||
updateTextOverflow() {
|
||||
this.displayedCardList.forEach((item, index) => {
|
||||
const el = this.$refs[`caseInfoContent-${index}`];
|
||||
if (el && el[0]) {
|
||||
item.isTextOverflow =
|
||||
el[0].clientHeight < el[0].scrollHeight;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 开始自动轮播
|
||||
startAutoPlay() {
|
||||
// 只有在有多项数据时才启动自动轮播
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
this.stopAutoPlay() // 先清除已有计时器
|
||||
this.timer = setInterval(() => {
|
||||
this.nextSlide() // 自动轮播方向:从左到右
|
||||
}, this.interval)
|
||||
},
|
||||
|
||||
// 停止自动轮播
|
||||
stopAutoPlay() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
|
||||
// 下一张(向右切换)- 从左到右显示
|
||||
nextSlide() {
|
||||
// 如果只有一项数据,不执行切换
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// 当到达最后一个真实元素时,切换到复制的第一个元素
|
||||
if (this.currentIndex === this.cardList.length) {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex++
|
||||
|
||||
// 动画完成后,瞬间切换到真实的第一个元素
|
||||
setTimeout(() => {
|
||||
this.transitionDuration = '0s'
|
||||
this.currentIndex = 1
|
||||
}, 500)
|
||||
} else {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex++
|
||||
}
|
||||
},
|
||||
|
||||
// 上一张(向左切换)- 从右到左显示
|
||||
prevSlide() {
|
||||
// 如果只有一项数据,不执行切换
|
||||
if (this.cardList.length <= 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// 当在第一个真实元素时,先切换到复制的最后一个元素
|
||||
if (this.currentIndex === 1) {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex--
|
||||
|
||||
// 动画完成后,瞬间切换到真实的最后一个元素
|
||||
setTimeout(() => {
|
||||
this.transitionDuration = '0s'
|
||||
this.currentIndex = this.cardList.length
|
||||
}, 500)
|
||||
} else {
|
||||
this.transitionDuration = '0.5s'
|
||||
this.currentIndex--
|
||||
}
|
||||
},
|
||||
|
||||
// 查看更多
|
||||
onHandleViewMore() {
|
||||
this.$router.push({
|
||||
name: 'ProductCaseList',
|
||||
query: {
|
||||
id: this.productId,
|
||||
},
|
||||
})
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
watch: {
|
||||
cardList: {
|
||||
handler(newValue) {
|
||||
if (newValue <= 1) {
|
||||
this.extendedCardList = newValue
|
||||
} else {
|
||||
this.extendedCardList = [
|
||||
this.cardList[this.cardList.length - 1], // 最后一个元素的副本放在开头
|
||||
...this.cardList, // 原始列表
|
||||
this.cardList[0], // 第一个元素的副本放在末尾
|
||||
]
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.extendedCardList.forEach((item, index) => {
|
||||
item.isTextOverflow =
|
||||
this.$refs[`caseInfoContent-${index}`][0]
|
||||
.clientHeight <
|
||||
this.$refs[`caseInfoContent-${index}`][0]
|
||||
.scrollHeight
|
||||
})
|
||||
})
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
showAllCases() {
|
||||
this.displayedCardList = this.cardList;
|
||||
this.showAll = true;
|
||||
this.showLoadMoreButton = false; // 隐藏按钮
|
||||
},
|
||||
|
||||
onHandleViewMore() {
|
||||
this.$router.push({
|
||||
name: 'ProductCaseList',
|
||||
query: {
|
||||
id: this.productId,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 查看详情
|
||||
handleCaseDetail(service) {
|
||||
this.$router.push({
|
||||
name: 'ProductCaseDetail',
|
||||
params: {id: service.id},
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.case-container {
|
||||
width: 100%;
|
||||
padding: 0 14px 20px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
width: 100%;
|
||||
padding: 0 14px 20px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.case-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
|
||||
& span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
& span:first-child {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
& span:last-child {
|
||||
font-size: 14px;
|
||||
background: linear-gradient(90deg, #00c7ef 0%, #005eef 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
// 轮播容器样式 - 确保不会显示溢出内容
|
||||
.carousel-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
// padding: 0 10px;
|
||||
// 添加额外的裁剪区域,确保不会显示相邻项的边缘
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
|
||||
// 轮播轨道样式
|
||||
.carousel-track {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
will-change: transform; // 优化动画性能
|
||||
}
|
||||
|
||||
// 单个案例项样式 - 确保占满整个宽度,不会露出相邻项
|
||||
.case-item {
|
||||
width: 100%; // 确保每个项目占满容器宽度
|
||||
flex-shrink: 0; // 防止项目被压缩
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arrow-left,
|
||||
.arrow-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translateY(-50%);
|
||||
& span:last-child {
|
||||
font-size: 14px;
|
||||
background: linear-gradient(90deg, #00c7ef 0%, #005eef 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
cursor: pointer;
|
||||
z-index: 10; // 确保按钮在内容上方
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
border-radius: 50%;
|
||||
padding: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
left: 10px;
|
||||
.case-grid-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
right: 10px;
|
||||
.case-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
column-gap:60px ;
|
||||
row-gap: 35px
|
||||
}
|
||||
|
||||
.arrow-left:hover,
|
||||
.arrow-right:hover {
|
||||
background-color: white;
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
.case-item-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s ease;
|
||||
border-bottom: 30px;
|
||||
}
|
||||
|
||||
.case-item-grid:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.case-left-img {
|
||||
width: 320px;
|
||||
height: 160px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.case-right-content {
|
||||
flex: 1;
|
||||
height: 160px;
|
||||
padding-left: 36px;
|
||||
background-color: #f0f0f0;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 1px;
|
||||
text-transform: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
h3 {
|
||||
padding-top: 36px;
|
||||
height: 70px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
div {
|
||||
// width: 100%;
|
||||
// height: 90px;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1.8;
|
||||
display: -webkit-box; /* 将元素作为弹性伸缩盒子模型显示 */
|
||||
-webkit-box-orient: vertical; /* 设置伸缩盒子的子元素排列方式为垂直排列 */
|
||||
-webkit-line-clamp: 2; /* 限制显示的行数为4行 */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
}
|
||||
|
||||
|
||||
.product-info-content {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-carousel__arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .el-carousel__button {
|
||||
background-color: #827e7e;
|
||||
height: 4px;
|
||||
width: 18px;
|
||||
border-radius: 2px;
|
||||
.load-more-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.product-info-tooltip {
|
||||
max-width: 80vw !important;
|
||||
padding: 6px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
max-width: 80vw !important;
|
||||
padding: 6px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -53,14 +53,17 @@
|
|||
</div>
|
||||
|
||||
<div class="product-container" v-else>
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<!-- <el-tooltip effect="dark" placement="top">
|
||||
<div slot="content" class="product-info-tooltip">
|
||||
{{ productDetail.introduction }}
|
||||
</div>
|
||||
<div class="product-info-content">
|
||||
{{ productDetail.introduction }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-tooltip>-->
|
||||
<div class="product-info-content">
|
||||
{{ productDetail.introduction }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue