初始化参股

This commit is contained in:
2026-01-27 18:06:04 +08:00
commit 2774a539bf
254 changed files with 33255 additions and 0 deletions

View File

@@ -0,0 +1,438 @@
<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.afterImages[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'
// 案例详情类型
type CaseDetail = {
id : string
title : string
category : string
categoryName : string
beforeImages : string[]
afterImages : 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: [],
description: '',
material: '',
duration: '',
price: '',
views: 0,
likes: 0,
createTime: ''
})
// 是否收藏
const isFavorite = ref(false)
// 获取案例详情
const fetchCaseDetail = async () => {
try {
const res = await getCaseDetail(caseId.value)
const data = res.data as UTSJSONObject
caseDetail.value = {
id: data['id'] as string,
title: data['title'] as string,
category: data['category'] as string,
categoryName: data['categoryName'] as string,
beforeImages: data['beforeImages'] as string[],
afterImages: data['afterImages'] as string[],
description: data['description'] as string,
material: data['material'] as string,
duration: data['duration'] as string,
price: data['price'] as string,
views: data['views'] as number,
likes: data['likes'] as number,
createTime: data['createTime'] as string
} as CaseDetail
// 更新标题
uni.setNavigationBarTitle({
title: caseDetail.value.title
})
} catch (e) {
console.error('获取案例详情失败', e)
}
}
// 预览图片
const previewImages = (index : number) => {
uni.previewImage({
current: index,
urls: caseDetail.value.afterImages
})
}
// 收藏
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>

View File

@@ -0,0 +1,272 @@
<template>
<view class="page">
<!-- 分类筛选 -->
<view class="category-bar">
<scroll-view class="category-scroll" scroll-x>
<view class="category-list">
<view
class="category-item"
:class="{ 'category-active': currentCategory == item.id }"
v-for="item in categories"
:key="item.id"
@click="selectCategory(item.id)"
>
<text
class="category-text"
:class="{ 'category-text-active': currentCategory == item.id }"
>{{ item.name }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 案例列表 -->
<scroll-view
class="case-scroll"
scroll-y
@scrolltolower="loadMore"
>
<view class="case-list">
<case-card
v-for="item in caseList"
:key="item.id"
:caseData="item"
@click="goToDetail"
></case-card>
</view>
<!-- 加载状态 -->
<view class="load-status">
<text class="load-text" v-if="loading">加载中...</text>
<text class="load-text" v-else-if="noMore">没有更多了</text>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="!loading && caseList.length == 0">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无相关案例</text>
</view>
<!-- 底部间距 -->
<view class="bottom-space"></view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { getCaseList } from '@/api/index.uts'
import { SOFA_CATEGORIES, PAGE_SIZE } from '@/utils/config.uts'
// 分类类型
type CategoryItem = {
id : string
name : string
}
// 案例类型
type CaseItem = {
id : string
title : string
category : string
categoryName : string
coverImage : string
material : string
duration : string
price : string
views : number
likes : number
}
// 分类列表
const categories = ref<CategoryItem[]>([])
// 当前分类
const currentCategory = ref('all')
// 案例列表
const caseList = ref<CaseItem[]>([])
// 分页
const page = ref(1)
const total = ref(0)
// 加载状态
const loading = ref(false)
const noMore = ref(false)
// 初始化分类
const initCategories = () => {
categories.value = SOFA_CATEGORIES.map((item) : CategoryItem => {
return {
id: item.id,
name: item.name
} as CategoryItem
})
}
// 选择分类
const selectCategory = (id : string) => {
if (currentCategory.value == id) return
currentCategory.value = id
page.value = 1
caseList.value = []
noMore.value = false
fetchCaseList()
}
// 获取案例列表
const fetchCaseList = async () => {
if (loading.value || noMore.value) return
loading.value = true
try {
const params = {
category: currentCategory.value,
page: page.value,
pageSize: PAGE_SIZE
} as UTSJSONObject
const res = await getCaseList(params)
const data = res.data as UTSJSONObject
// 适应后端返回格式:{ items: [], total: 0, page: 1, limit: 10 }
const list = data['items'] as UTSJSONObject[] || []
total.value = data['total'] as number || 0
const newList = list.map((item) : CaseItem => {
return {
id: item['id'] as string,
title: item['title'] as string,
category: item['category'] as string,
categoryName: item['categoryName'] as string,
coverImage: item['coverImage'] as string,
material: item['material'] as string,
duration: item['duration'] as string,
price: item['price'] as string,
views: item['views'] as number,
likes: item['likes'] as number
} as CaseItem
})
if (page.value == 1) {
caseList.value = newList
} else {
caseList.value = [...caseList.value, ...newList]
}
if (caseList.value.length >= total.value) {
noMore.value = true
}
} catch (e) {
console.error('获取案例列表失败', e)
}
loading.value = false
}
// 加载更多
const loadMore = () => {
if (!noMore.value && !loading.value) {
page.value++
fetchCaseList()
}
}
// 跳转详情
const goToDetail = (id : string) => {
uni.navigateTo({
url: `/pages/cases/detail?id=${id}`
})
}
onLoad(() => {
initCategories()
fetchCaseList()
})
</script>
<style lang="scss">
.page {
flex: 1;
background-color: #f5f5f5;
}
/* 分类栏 */
.category-bar {
background-color: #ffffff;
padding: 24rpx 0;
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #EBEEF5;
}
.category-scroll {
flex-direction: row;
}
.category-scroll ::-webkit-scrollbar {
display: none;
}
.category-list {
flex-direction: row;
padding: 0 24rpx;
}
.category-item {
padding: 16rpx 32rpx;
margin-right: 16rpx;
border-radius: 999rpx;
background-color: #f5f5f5;
}
.category-active {
background-color: #D4A574;
}
.category-text {
font-size: 28rpx;
color: #606266;
}
.category-text-active {
color: #ffffff;
}
/* 案例列表 */
.case-scroll {
flex: 1;
}
.case-list {
padding: 24rpx;
}
/* 加载状态 */
.load-status {
padding: 32rpx 0;
align-items: center;
}
.load-text {
font-size: 26rpx;
color: #909399;
}
/* 空状态 */
.empty-state {
padding: 120rpx 0;
align-items: center;
}
.empty-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 28rpx;
color: #909399;
}
/* 底部间距 */
.bottom-space {
height: 120rpx;
}
</style>