Files
ShaFaFanXin/前端/pages/cases/detail.uvue

469 lines
9.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<scroll-view class="page-scroll" scroll-y>
<!-- 图片画廊 -->
<view class="gallery-section">
<swiper
class="gallery-swiper"
circular
indicator-dots
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#ffffff"
>
<swiper-item v-for="(item, index) in caseDetail.afterImages" :key="index">
<image
class="gallery-image"
:src="item"
mode="aspectFill"
@click="previewImages(index)"
></image>
</swiper-item>
</swiper>
<view class="gallery-tag">
<text class="gallery-tag-text">{{ caseDetail.categoryName }}</text>
</view>
</view>
<!-- 基本信息 -->
<view class="info-section">
<text class="case-title">{{ caseDetail.title }}</text>
<view class="case-meta">
<text class="case-price">{{ caseDetail.price }}</text>
<view class="case-stats">
<text class="stat-text">👁 {{ caseDetail.views }}</text>
<text class="stat-text">❤ {{ caseDetail.likes }}</text>
</view>
</view>
</view>
<!-- 翻新前后对比 -->
<view class="compare-section">
<before-after
:beforeImage="caseDetail.beforeImages[0] || ''"
:afterImage="caseDetail.compareAfterImages[0] || ''"
:showTitle="true"
></before-after>
</view>
<!-- 详细信息 -->
<view class="detail-section">
<view class="detail-header">
<text class="detail-title">翻新详情</text>
</view>
<view class="detail-list">
<view class="detail-item">
<text class="detail-label">使用材质</text>
<text class="detail-value">{{ caseDetail.material }}</text>
</view>
<view class="detail-item">
<text class="detail-label">翻新工期</text>
<text class="detail-value">{{ caseDetail.duration }}</text>
</view>
<view class="detail-item">
<text class="detail-label">完成日期</text>
<text class="detail-value">{{ caseDetail.createTime }}</text>
</view>
</view>
<view class="detail-desc">
<text class="desc-title">案例描述</text>
<text class="desc-content">{{ caseDetail.description }}</text>
</view>
</view>
<!-- 底部间距 -->
<view class="bottom-space"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="bar-left">
<view class="bar-btn" @click="handleFavorite">
<text class="bar-icon">{{ isFavorite ? '❤️' : '🤍' }}</text>
<text class="bar-label">收藏</text>
</view>
<view class="bar-btn" @click="handleShare">
<text class="bar-icon">📤</text>
<text class="bar-label">分享</text>
</view>
</view>
<view class="bar-right">
<view class="contact-btn" @click="handleContact">
<text class="contact-text">在线咨询</text>
</view>
<view class="booking-btn" @click="goToBooking">
<text class="booking-text">立即预约</text>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { getCaseDetail } from '@/api/index.uts'
import { getServiceTypeName } from '@/utils/config.uts'
// 案例详情类型
type CaseDetail = {
id : string
title : string
category : string
categoryName : string
beforeImages : string[]
afterImages : string[]
// 用于对比组件的专用后图(不使用 images 回退)
compareAfterImages : string[]
description : string
material : string
duration : string
price : string
views : number
likes : number
createTime : string
}
// 案例ID
const caseId = ref('')
// 案例详情
const caseDetail = ref<CaseDetail>({
id: '',
title: '',
category: '',
categoryName: '',
beforeImages: [],
afterImages: [],
compareAfterImages: [],
description: '',
material: '',
duration: '',
price: '',
views: 0,
likes: 0,
createTime: ''
})
// 是否收藏
const isFavorite = ref(false)
// 获取案例详情
const fetchCaseDetail = async () => {
try {
const res = await getCaseDetail(caseId.value)
if (res.code === 0 && res.data != null) {
// 处理成功的返回数据
const data = res.data as UTSJSONObject
// 适配后端返回的字段
const images = data['images'] as string[] || []
const beforeImages = data['beforeImages'] as string[] || []
const afterImages = data['afterImages'] as string[] || []
const createdAt = data['createdAt'] as string || ''
// 画廊展示如果afterImages为空使用images作为回退
const displayAfterImages = afterImages.length > 0 ? afterImages : images
// 对比组件before-after应只使用专用的 afterImages不回退到 images
const compareAfterImages = afterImages.length > 0 ? afterImages : []
caseDetail.value = {
id: String(data['id']),
title: data['title'] as string,
category: data['serviceType'] as string || '',
categoryName: getServiceTypeName(data['serviceType'] as string),
beforeImages: beforeImages,
afterImages: displayAfterImages,
compareAfterImages: compareAfterImages,
description: data['description'] as string,
material: data['materials'] as string || '优质材料',
duration: (data['duration'] as number || 0) + '天',
price: data['price'] != null ? '¥' + data['price'] : '面议',
views: data['views'] as number || 0,
likes: data['likes'] as number || 0,
createTime: createdAt.split('T')[0] || ''
} as CaseDetail
// 更新标题
uni.setNavigationBarTitle({
title: caseDetail.value.title
})
} else {
uni.showToast({
title: res.message || '加载失败',
icon: 'none'
})
}
} catch (error) {
console.error('获取案例详情失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
}
}
// 预览图片
const previewImages = (index : number) => {
const urls = caseDetail.value.afterImages || []
const current = urls[index] || urls[0] || ''
uni.previewImage({
current: current,
urls: urls
})
}
// 收藏
const handleFavorite = () => {
isFavorite.value = !isFavorite.value
uni.showToast({
title: isFavorite.value ? '已收藏' : '已取消收藏',
icon: 'none'
})
}
// 分享
const handleShare = () => {
// #ifdef MP-WEIXIN
// 微信小程序触发分享
// #endif
uni.showToast({
title: '分享功能开发中',
icon: 'none'
})
}
// 在线咨询
const handleContact = () => {
uni.makePhoneCall({
phoneNumber: '400-888-8888',
fail: () => {
uni.showToast({
title: '拨打电话失败',
icon: 'none'
})
}
})
}
// 预约
const goToBooking = () => {
uni.navigateTo({
url: '/pages/booking/index'
})
}
onLoad((options : OnLoadOptions) => {
caseId.value = options['id'] ?? ''
if (caseId.value != '') {
fetchCaseDetail()
}
})
</script>
<style lang="scss">
.page {
flex: 1;
background-color: #f5f5f5;
}
.page-scroll {
flex: 1;
}
/* 图片画廊 */
.gallery-section {
position: relative;
}
.gallery-swiper {
height: 500rpx;
}
.gallery-image {
width: 100%;
height: 100%;
}
.gallery-tag {
position: absolute;
top: 24rpx;
left: 24rpx;
background-color: rgba(212, 165, 116, 0.9);
padding: 8rpx 20rpx;
border-radius: 8rpx;
}
.gallery-tag-text {
font-size: 24rpx;
color: #ffffff;
}
/* 基本信息 */
.info-section {
background-color: #ffffff;
padding: 32rpx;
margin-bottom: 24rpx;
}
.case-title {
font-size: 36rpx;
font-weight: 600;
color: #333333;
margin-bottom: 24rpx;
}
.case-meta {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.case-price {
font-size: 40rpx;
font-weight: 600;
color: #D4A574;
}
.case-stats {
flex-direction: row;
}
.stat-text {
font-size: 26rpx;
color: #909399;
margin-left: 24rpx;
}
/* 对比区域 */
.compare-section {
padding: 0 24rpx;
}
/* 详细信息 */
.detail-section {
background-color: #ffffff;
margin: 24rpx;
border-radius: 16rpx;
padding: 32rpx;
}
.detail-header {
margin-bottom: 24rpx;
}
.detail-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.detail-list {
margin-bottom: 32rpx;
}
.detail-item {
flex-direction: row;
justify-content: space-between;
padding: 16rpx 0;
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #EBEEF5;
}
.detail-label {
font-size: 28rpx;
color: #909399;
}
.detail-value {
font-size: 28rpx;
color: #333333;
}
.detail-desc {
padding-top: 16rpx;
}
.desc-title {
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.desc-content {
font-size: 28rpx;
color: #606266;
line-height: 48rpx;
}
/* 底部间距 */
.bottom-space {
height: 160rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
padding: 16rpx 24rpx;
padding-bottom: 32rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.bar-left {
flex-direction: row;
}
.bar-btn {
align-items: center;
padding: 0 24rpx;
}
.bar-icon {
font-size: 40rpx;
}
.bar-label {
font-size: 22rpx;
color: #606266;
margin-top: 4rpx;
}
.bar-right {
flex-direction: row;
flex: 1;
justify-content: flex-end;
}
.contact-btn {
background-color: #f5f5f5;
padding: 20rpx 40rpx;
border-radius: 999rpx;
margin-right: 16rpx;
}
.contact-text {
font-size: 28rpx;
color: #606266;
}
.booking-btn {
background-color: #D4A574;
padding: 20rpx 48rpx;
border-radius: 999rpx;
}
.booking-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
</style>